From: Sylvestre Ledru Date: Sun, 16 Dec 2018 12:07:54 +0000 (+0000) Subject: Import llvm-toolchain-7_7.0.1.orig-clang-tools-extra.tar.bz2 X-Git-Tag: archive/raspbian/1%7.0.1-1+rpi1^2~90^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=c2cb78ae5825d40de5f67ed1b9473d2849dae92c;p=llvm-toolchain-7.git Import llvm-toolchain-7_7.0.1.orig-clang-tools-extra.tar.bz2 [dgit import orig llvm-toolchain-7_7.0.1.orig-clang-tools-extra.tar.bz2] --- c2cb78ae5825d40de5f67ed1b9473d2849dae92c diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 000000000..d4a00161b --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "repository.callsign" : "CTE", + "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..c434682cf --- /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-doc) +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. +if(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..b886535dc --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,63 @@ +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2018 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..35c321aed --- /dev/null +++ b/change-namespace/ChangeNamespace.cpp @@ -0,0 +1,1015 @@ +//===-- 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(functionDecl(isDefaulted())))), + 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(functionDecl(isDefaulted())), + 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( + expr(allOf(hasAncestor(decl().bind("dc")), IsInMovedNs, + unless(hasAncestor(isImplicit())), + anyOf(callExpr(callee(FuncMatcher)).bind("call"), + declRefExpr(to(FuncMatcher.bind("func_decl"))) + .bind("func_ref")))), + 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(); + // This happens for friend declaration of a base class with injected class + // name. + if (!NestedNameSpecifier.getNestedNameSpecifier()) + return; + 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..f1d8ddb27 --- /dev/null +++ b/change-namespace/tool/CMakeLists.txt @@ -0,0 +1,24 @@ +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 + PRIVATE + 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..02da0851a --- /dev/null +++ b/clang-apply-replacements/CMakeLists.txt @@ -0,0 +1,20 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangApplyReplacements + lib/Tooling/ApplyReplacements.cpp + + LINK_LIBS + clangAST + clangBasic + clangRewrite + clangToolingCore + clangToolingRefactor + ) + +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..da9ae0c91 --- /dev/null +++ b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h @@ -0,0 +1,123 @@ +//===-- 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 "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +namespace clang { + +class DiagnosticsEngine; +class Rewriter; + +namespace replace { + +/// \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 a set of AtomicChange targeting that file. +typedef llvm::DenseMap> + FileToChangesMap; + +/// \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 extract all Replacements stored +/// in \c TUs. Conflicting replacements are skipped. +/// +/// \post For all (key,value) in FileChanges, value[i].getOffset() <= +/// value[i+1].getOffset(). +/// +/// \param[in] TUs Collection of TranslationUnitReplacements or +/// TranslationUnitDiagnostics to merge, deduplicate, and test for conflicts. +/// \param[out] FileChanges Container grouping all changes by the +/// file they target. Only non conflicting replacements are kept into +/// FileChanges. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true If all changes were converted successfully. +/// \li false If there were conflicts. +bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs, + FileToChangesMap &FileChanges, + clang::SourceManager &SM); + +/// \brief Apply \c AtomicChange on File and rewrite it. +/// +/// \param[in] File Path of the file where to apply AtomicChange. +/// \param[in] Changes to apply. +/// \param[in] Spec For code cleanup and formatting. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns The changed code if all changes are applied successfully; +/// otherwise, an llvm::Error carrying llvm::StringError or an error_code. +llvm::Expected +applyChanges(StringRef File, const std::vector &Changes, + const tooling::ApplyChangesSpec &Spec, + DiagnosticsEngine &Diagnostics); + +/// \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..1970d05c6 --- /dev/null +++ b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp @@ -0,0 +1,249 @@ +//===-- 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 Extract replacements from collected TranslationUnitReplacements and +/// TranslationUnitDiagnostics and group them per file. +/// +/// \param[in] TUs Collection of all found and deserialized +/// TranslationUnitReplacements. +/// \param[in] TUDs Collection of all found and deserialized +/// TranslationUnitDiagnostics. +/// \param[in] SM Used to deduplicate paths. +/// +/// \returns A map mapping FileEntry to a set of Replacement targeting that +/// file. +static llvm::DenseMap> +groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs, + const clang::SourceManager &SM) { + std::set Warned; + llvm::DenseMap> + GroupedReplacements; + + auto AddToGroup = [&](const tooling::Replacement &R) { + // Use the file manager to deduplicate paths. FileEntries are + // automatically canonicalized. + if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) { + GroupedReplacements[Entry].push_back(R); + } else if (Warned.insert(R.getFilePath()).second) { + errs() << "Described file '" << R.getFilePath() + << "' doesn't exist. Ignoring...\n"; + } + }; + + for (const auto &TU : TUs) + for (const tooling::Replacement &R : TU.Replacements) + AddToGroup(R); + + for (const auto &TU : TUDs) + for (const auto &D : TU.Diagnostics) + for (const auto &Fix : D.Fix) + for (const tooling::Replacement &R : Fix.second) + AddToGroup(R); + + // Sort replacements per file to keep consistent behavior when + // clang-apply-replacements run on differents machine. + for (auto &FileAndReplacements : GroupedReplacements) { + llvm::sort(FileAndReplacements.second.begin(), + FileAndReplacements.second.end()); + } + + return GroupedReplacements; +} + +bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs, + FileToChangesMap &FileChanges, + clang::SourceManager &SM) { + auto GroupedReplacements = groupReplacements(TUs, TUDs, SM); + bool ConflictDetected = false; + + // To report conflicting replacements on corresponding file, all replacements + // are stored into 1 big AtomicChange. + for (const auto &FileAndReplacements : GroupedReplacements) { + const FileEntry *Entry = FileAndReplacements.first; + const SourceLocation BeginLoc = + SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User)); + tooling::AtomicChange FileChange(Entry->getName(), Entry->getName()); + for (const auto &R : FileAndReplacements.second) { + llvm::Error Err = + FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()), + R.getLength(), R.getReplacementText()); + if (Err) { + // FIXME: This will report conflicts by pair using a file+offset format + // which is not so much human readable. + // A first improvement could be to translate offset to line+col. For + // this and without loosing error message some modifications arround + // `tooling::ReplacementError` are need (access to + // `getReplacementErrString`). + // A better strategy could be to add a pretty printer methods for + // conflict reporting. Methods that could be parameterized to report a + // conflict in different format, file+offset, file+line+col, or even + // more human readable using VCS conflict markers. + // For now, printing directly the error reported by `AtomicChange` is + // the easiest solution. + errs() << llvm::toString(std::move(Err)) << "\n"; + ConflictDetected = true; + } + } + FileChanges.try_emplace(Entry, + std::vector{FileChange}); + } + + return !ConflictDetected; +} + +llvm::Expected +applyChanges(StringRef File, const std::vector &Changes, + const tooling::ApplyChangesSpec &Spec, + DiagnosticsEngine &Diagnostics) { + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + + llvm::ErrorOr> Buffer = + SM.getFileManager().getBufferForFile(File); + if (!Buffer) + return errorCodeToError(Buffer.getError()); + return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes, + Spec); +} + +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..945b48610 --- /dev/null +++ b/clang-apply-replacements/tool/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_tool(clang-apply-replacements + ClangApplyReplacementsMain.cpp + ) +target_link_libraries(clang-apply-replacements + PRIVATE + clangApplyReplacements + clangBasic + clangFormat + clangRewrite + clangToolingCore + clangToolingRefactor + ) + +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..24a430f0d --- /dev/null +++ b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp @@ -0,0 +1,165 @@ +//===-- 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(raw_ostream &OS) { + OS << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n"; +} + +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. + auto FormatStyleOrError = + format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM"); + if (!FormatStyleOrError) { + llvm::errs() << llvm::toString(FormatStyleOrError.takeError()) << "\n"; + return 1; + } + format::FormatStyle FormatStyle = std::move(*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); + + FileToChangesMap Changes; + if (!mergeAndDeduplicate(TURs, TUDs, Changes, SM)) + return 1; + + tooling::ApplyChangesSpec Spec; + Spec.Cleanup = true; + Spec.Style = FormatStyle; + Spec.Format = DoFormat ? tooling::ApplyChangesSpec::kAll + : tooling::ApplyChangesSpec::kNone; + + for (const auto &FileChange : Changes) { + const FileEntry *Entry = FileChange.first; + StringRef FileName = Entry->getName(); + llvm::Expected NewFileData = + applyChanges(FileName, FileChange.second, Spec, Diagnostics); + if (!NewFileData) { + errs() << llvm::toString(NewFileData.takeError()) << "\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-doc/BitcodeReader.cpp b/clang-doc/BitcodeReader.cpp new file mode 100644 index 000000000..fa51d0135 --- /dev/null +++ b/clang-doc/BitcodeReader.cpp @@ -0,0 +1,622 @@ +//===-- BitcodeReader.cpp - ClangDoc Bitcode Reader ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BitcodeReader.h" +#include "llvm/ADT/IndexedMap.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace doc { + +using Record = llvm::SmallVector; + +bool decodeRecord(Record R, llvm::SmallVectorImpl &Field, + llvm::StringRef Blob) { + Field.assign(Blob.begin(), Blob.end()); + return true; +} + +bool decodeRecord(Record R, SymbolID &Field, llvm::StringRef Blob) { + if (R[0] != BitCodeConstants::USRHashSize) + return false; + + // First position in the record is the length of the following array, so we + // copy the following elements to the field. + for (int I = 0, E = R[0]; I < E; ++I) + Field[I] = R[I + 1]; + return true; +} + +bool decodeRecord(Record R, bool &Field, llvm::StringRef Blob) { + Field = R[0] != 0; + return true; +} + +bool decodeRecord(Record R, int &Field, llvm::StringRef Blob) { + if (R[0] > INT_MAX) + return false; + Field = (int)R[0]; + return true; +} + +bool decodeRecord(Record R, AccessSpecifier &Field, llvm::StringRef Blob) { + switch (R[0]) { + case AS_public: + case AS_private: + case AS_protected: + case AS_none: + Field = (AccessSpecifier)R[0]; + return true; + default: + return false; + } +} + +bool decodeRecord(Record R, TagTypeKind &Field, llvm::StringRef Blob) { + switch (R[0]) { + case TTK_Struct: + case TTK_Interface: + case TTK_Union: + case TTK_Class: + case TTK_Enum: + Field = (TagTypeKind)R[0]; + return true; + default: + return false; + } +} + +bool decodeRecord(Record R, llvm::Optional &Field, + llvm::StringRef Blob) { + if (R[0] > INT_MAX) + return false; + Field.emplace((int)R[0], Blob); + return true; +} + +bool decodeRecord(Record R, InfoType &Field, llvm::StringRef Blob) { + switch (auto IT = static_cast(R[0])) { + case InfoType::IT_namespace: + case InfoType::IT_record: + case InfoType::IT_function: + case InfoType::IT_default: + case InfoType::IT_enum: + Field = IT; + return true; + } + return false; +} + +bool decodeRecord(Record R, FieldId &Field, llvm::StringRef Blob) { + switch (auto F = static_cast(R[0])) { + case FieldId::F_namespace: + case FieldId::F_parent: + case FieldId::F_vparent: + case FieldId::F_type: + case FieldId::F_default: + Field = F; + return true; + } + return false; +} + +bool decodeRecord(Record R, llvm::SmallVectorImpl> &Field, + llvm::StringRef Blob) { + Field.push_back(Blob); + return true; +} + +bool decodeRecord(Record R, llvm::SmallVectorImpl &Field, + llvm::StringRef Blob) { + if (R[0] > INT_MAX) + return false; + Field.emplace_back((int)R[0], Blob); + return true; +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, + const unsigned VersionNo) { + if (ID == VERSION && R[0] == VersionNo) + return true; + return false; +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, + NamespaceInfo *I) { + switch (ID) { + case NAMESPACE_USR: + return decodeRecord(R, I->USR, Blob); + case NAMESPACE_NAME: + return decodeRecord(R, I->Name, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, RecordInfo *I) { + switch (ID) { + case RECORD_USR: + return decodeRecord(R, I->USR, Blob); + case RECORD_NAME: + return decodeRecord(R, I->Name, Blob); + case RECORD_DEFLOCATION: + return decodeRecord(R, I->DefLoc, Blob); + case RECORD_LOCATION: + return decodeRecord(R, I->Loc, Blob); + case RECORD_TAG_TYPE: + return decodeRecord(R, I->TagType, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, EnumInfo *I) { + switch (ID) { + case ENUM_USR: + return decodeRecord(R, I->USR, Blob); + case ENUM_NAME: + return decodeRecord(R, I->Name, Blob); + case ENUM_DEFLOCATION: + return decodeRecord(R, I->DefLoc, Blob); + case ENUM_LOCATION: + return decodeRecord(R, I->Loc, Blob); + case ENUM_MEMBER: + return decodeRecord(R, I->Members, Blob); + case ENUM_SCOPED: + return decodeRecord(R, I->Scoped, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, FunctionInfo *I) { + switch (ID) { + case FUNCTION_USR: + return decodeRecord(R, I->USR, Blob); + case FUNCTION_NAME: + return decodeRecord(R, I->Name, Blob); + case FUNCTION_DEFLOCATION: + return decodeRecord(R, I->DefLoc, Blob); + case FUNCTION_LOCATION: + return decodeRecord(R, I->Loc, Blob); + case FUNCTION_ACCESS: + return decodeRecord(R, I->Access, Blob); + case FUNCTION_IS_METHOD: + return decodeRecord(R, I->IsMethod, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, TypeInfo *I) { + return true; +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, + FieldTypeInfo *I) { + switch (ID) { + case FIELD_TYPE_NAME: + return decodeRecord(R, I->Name, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, + MemberTypeInfo *I) { + switch (ID) { + case MEMBER_TYPE_NAME: + return decodeRecord(R, I->Name, Blob); + case MEMBER_TYPE_ACCESS: + return decodeRecord(R, I->Access, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, CommentInfo *I) { + switch (ID) { + case COMMENT_KIND: + return decodeRecord(R, I->Kind, Blob); + case COMMENT_TEXT: + return decodeRecord(R, I->Text, Blob); + case COMMENT_NAME: + return decodeRecord(R, I->Name, Blob); + case COMMENT_DIRECTION: + return decodeRecord(R, I->Direction, Blob); + case COMMENT_PARAMNAME: + return decodeRecord(R, I->ParamName, Blob); + case COMMENT_CLOSENAME: + return decodeRecord(R, I->CloseName, Blob); + case COMMENT_ATTRKEY: + return decodeRecord(R, I->AttrKeys, Blob); + case COMMENT_ATTRVAL: + return decodeRecord(R, I->AttrValues, Blob); + case COMMENT_ARG: + return decodeRecord(R, I->Args, Blob); + case COMMENT_SELFCLOSING: + return decodeRecord(R, I->SelfClosing, Blob); + case COMMENT_EXPLICIT: + return decodeRecord(R, I->Explicit, Blob); + default: + return false; + } +} + +bool parseRecord(Record R, unsigned ID, llvm::StringRef Blob, Reference *I, + FieldId &F) { + switch (ID) { + case REFERENCE_USR: + return decodeRecord(R, I->USR, Blob); + case REFERENCE_NAME: + return decodeRecord(R, I->Name, Blob); + case REFERENCE_TYPE: + return decodeRecord(R, I->RefType, Blob); + case REFERENCE_FIELD: + return decodeRecord(R, F, Blob); + default: + return false; + } +} + +template CommentInfo *getCommentInfo(T I) { + llvm::errs() << "Cannot have comment subblock.\n"; + exit(1); +} + +template <> CommentInfo *getCommentInfo(FunctionInfo *I) { + I->Description.emplace_back(); + return &I->Description.back(); +} + +template <> CommentInfo *getCommentInfo(NamespaceInfo *I) { + I->Description.emplace_back(); + return &I->Description.back(); +} + +template <> CommentInfo *getCommentInfo(RecordInfo *I) { + I->Description.emplace_back(); + return &I->Description.back(); +} + +template <> CommentInfo *getCommentInfo(EnumInfo *I) { + I->Description.emplace_back(); + return &I->Description.back(); +} + +template <> CommentInfo *getCommentInfo(CommentInfo *I) { + I->Children.emplace_back(llvm::make_unique()); + return I->Children.back().get(); +} + +template <> CommentInfo *getCommentInfo(std::unique_ptr &I) { + return getCommentInfo(I.get()); +} + +template +void addTypeInfo(T I, TTypeInfo &&TI) { + llvm::errs() << "Invalid type for info.\n"; + exit(1); +} + +template <> void addTypeInfo(RecordInfo *I, MemberTypeInfo &&T) { + I->Members.emplace_back(std::move(T)); +} + +template <> void addTypeInfo(FunctionInfo *I, TypeInfo &&T) { + I->ReturnType = std::move(T); +} + +template <> void addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) { + I->Params.emplace_back(std::move(T)); +} + +template void addReference(T I, Reference &&R, FieldId F) { + llvm::errs() << "Invalid field type for info.\n"; + exit(1); +} + +template <> void addReference(TypeInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_type: + I->Type = std::move(R); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +template <> void addReference(FieldTypeInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_type: + I->Type = std::move(R); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +template <> void addReference(MemberTypeInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_type: + I->Type = std::move(R); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +template <> void addReference(EnumInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_namespace: + I->Namespace.emplace_back(std::move(R)); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +template <> void addReference(NamespaceInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_namespace: + I->Namespace.emplace_back(std::move(R)); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +template <> void addReference(FunctionInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_namespace: + I->Namespace.emplace_back(std::move(R)); + break; + case FieldId::F_parent: + I->Parent = std::move(R); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +template <> void addReference(RecordInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_namespace: + I->Namespace.emplace_back(std::move(R)); + break; + case FieldId::F_parent: + I->Parents.emplace_back(std::move(R)); + break; + case FieldId::F_vparent: + I->VirtualParents.emplace_back(std::move(R)); + break; + default: + llvm::errs() << "Invalid field type for info.\n"; + exit(1); + } +} + +// Read records from bitcode into a given info. +template bool ClangDocBitcodeReader::readRecord(unsigned ID, T I) { + Record R; + llvm::StringRef Blob; + unsigned RecID = Stream.readRecord(ID, R, &Blob); + return parseRecord(R, RecID, Blob, I); +} + +template <> bool ClangDocBitcodeReader::readRecord(unsigned ID, Reference *I) { + Record R; + llvm::StringRef Blob; + unsigned RecID = Stream.readRecord(ID, R, &Blob); + return parseRecord(R, RecID, Blob, I, CurrentReferenceField); +} + +// Read a block of records into a single info. +template bool ClangDocBitcodeReader::readBlock(unsigned ID, T I) { + if (Stream.EnterSubBlock(ID)) + return false; + + while (true) { + unsigned BlockOrCode = 0; + Cursor Res = skipUntilRecordOrBlock(BlockOrCode); + + switch (Res) { + case Cursor::BadBlock: + return false; + case Cursor::BlockEnd: + return true; + case Cursor::BlockBegin: + if (readSubBlock(BlockOrCode, I)) + continue; + if (!Stream.SkipBlock()) + return false; + continue; + case Cursor::Record: + break; + } + if (!readRecord(BlockOrCode, I)) + return false; + } +} + +template +bool ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { + switch (ID) { + // Blocks can only have Comment, Reference, or TypeInfo subblocks + case BI_COMMENT_BLOCK_ID: + if (readBlock(ID, getCommentInfo(I))) + return true; + return false; + case BI_TYPE_BLOCK_ID: { + TypeInfo TI; + if (readBlock(ID, &TI)) { + addTypeInfo(I, std::move(TI)); + return true; + } + return false; + } + case BI_FIELD_TYPE_BLOCK_ID: { + FieldTypeInfo TI; + if (readBlock(ID, &TI)) { + addTypeInfo(I, std::move(TI)); + return true; + } + return false; + } + case BI_MEMBER_TYPE_BLOCK_ID: { + MemberTypeInfo TI; + if (readBlock(ID, &TI)) { + addTypeInfo(I, std::move(TI)); + return true; + } + return false; + } + case BI_REFERENCE_BLOCK_ID: { + Reference R; + if (readBlock(ID, &R)) { + addReference(I, std::move(R), CurrentReferenceField); + return true; + } + return false; + } + default: + llvm::errs() << "Invalid subblock type.\n"; + return false; + } +} + +ClangDocBitcodeReader::Cursor +ClangDocBitcodeReader::skipUntilRecordOrBlock(unsigned &BlockOrRecordID) { + BlockOrRecordID = 0; + + while (!Stream.AtEndOfStream()) { + unsigned Code = Stream.ReadCode(); + + switch ((llvm::bitc::FixedAbbrevIDs)Code) { + case llvm::bitc::ENTER_SUBBLOCK: + BlockOrRecordID = Stream.ReadSubBlockID(); + return Cursor::BlockBegin; + case llvm::bitc::END_BLOCK: + if (Stream.ReadBlockEnd()) + return Cursor::BadBlock; + return Cursor::BlockEnd; + case llvm::bitc::DEFINE_ABBREV: + Stream.ReadAbbrevRecord(); + continue; + case llvm::bitc::UNABBREV_RECORD: + return Cursor::BadBlock; + default: + BlockOrRecordID = Code; + return Cursor::Record; + } + } + llvm_unreachable("Premature stream end."); +} + +bool ClangDocBitcodeReader::validateStream() { + if (Stream.AtEndOfStream()) + return false; + + // Sniff for the signature. + if (Stream.Read(8) != BitCodeConstants::Signature[0] || + Stream.Read(8) != BitCodeConstants::Signature[1] || + Stream.Read(8) != BitCodeConstants::Signature[2] || + Stream.Read(8) != BitCodeConstants::Signature[3]) + return false; + return true; +} + +bool ClangDocBitcodeReader::readBlockInfoBlock() { + BlockInfo = Stream.ReadBlockInfoBlock(); + if (!BlockInfo) + return false; + Stream.setBlockInfo(&*BlockInfo); + return true; +} + +template +std::unique_ptr ClangDocBitcodeReader::createInfo(unsigned ID) { + std::unique_ptr I = llvm::make_unique(); + if (readBlock(ID, static_cast(I.get()))) + return I; + llvm::errs() << "Error reading from block.\n"; + return nullptr; +} + +std::unique_ptr ClangDocBitcodeReader::readBlockToInfo(unsigned ID) { + switch (ID) { + case BI_NAMESPACE_BLOCK_ID: + return createInfo(ID); + case BI_RECORD_BLOCK_ID: + return createInfo(ID); + case BI_ENUM_BLOCK_ID: + return createInfo(ID); + case BI_FUNCTION_BLOCK_ID: + return createInfo(ID); + default: + llvm::errs() << "Error reading from block.\n"; + return nullptr; + } +} + +// Entry point +std::vector> ClangDocBitcodeReader::readBitcode() { + std::vector> Infos; + if (!validateStream()) + return Infos; + + // Read the top level blocks. + while (!Stream.AtEndOfStream()) { + unsigned Code = Stream.ReadCode(); + if (Code != llvm::bitc::ENTER_SUBBLOCK) + return Infos; + + unsigned ID = Stream.ReadSubBlockID(); + switch (ID) { + // NamedType and Comment blocks should not appear at the top level + case BI_TYPE_BLOCK_ID: + case BI_FIELD_TYPE_BLOCK_ID: + case BI_MEMBER_TYPE_BLOCK_ID: + case BI_COMMENT_BLOCK_ID: + case BI_REFERENCE_BLOCK_ID: + llvm::errs() << "Invalid top level block.\n"; + return Infos; + case BI_NAMESPACE_BLOCK_ID: + case BI_RECORD_BLOCK_ID: + case BI_ENUM_BLOCK_ID: + case BI_FUNCTION_BLOCK_ID: + if (std::unique_ptr I = readBlockToInfo(ID)) { + Infos.emplace_back(std::move(I)); + } + return Infos; + case BI_VERSION_BLOCK_ID: + if (readBlock(ID, VersionNumber)) + continue; + return Infos; + case llvm::bitc::BLOCKINFO_BLOCK_ID: + if (readBlockInfoBlock()) + continue; + return Infos; + default: + if (!Stream.SkipBlock()) + continue; + } + } + return Infos; +} + +} // namespace doc +} // namespace clang diff --git a/clang-doc/BitcodeReader.h b/clang-doc/BitcodeReader.h new file mode 100644 index 000000000..c0cf24a17 --- /dev/null +++ b/clang-doc/BitcodeReader.h @@ -0,0 +1,73 @@ +//===-- BitcodeReader.h - ClangDoc Bitcode Reader --------------*- C++ -*-===// +// +// 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 reader for parsing the clang-doc internal +// representation from LLVM bitcode. The reader takes in a stream of bits and +// generates the set of infos that it represents. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEREADER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEREADER_H + +#include "BitcodeWriter.h" +#include "Representation.h" +#include "clang/AST/AST.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Bitcode/BitstreamReader.h" + +namespace clang { +namespace doc { + +// Class to read bitstream into an InfoSet collection +class ClangDocBitcodeReader { +public: + ClangDocBitcodeReader(llvm::BitstreamCursor &Stream) : Stream(Stream) {} + + // Main entry point, calls readBlock to read each block in the given stream. + std::vector> readBitcode(); + +private: + enum class Cursor { BadBlock = 1, Record, BlockEnd, BlockBegin }; + + // Top level parsing + bool validateStream(); + bool readVersion(); + bool readBlockInfoBlock(); + + // Read a block of records into a single Info struct, calls readRecord on each + // record found. + template bool readBlock(unsigned ID, T I); + + // Step through a block of records to find the next data field. + template bool readSubBlock(unsigned ID, T I); + + // Read record data into the given Info data field, calling the appropriate + // parseRecord functions to parse and store the data. + template bool readRecord(unsigned ID, T I); + + // Allocate the relevant type of info and add read data to the object. + template std::unique_ptr createInfo(unsigned ID); + + // Helper function to step through blocks to find and dispatch the next record + // or block to be read. + Cursor skipUntilRecordOrBlock(unsigned &BlockOrRecordID); + + // Helper function to set up the approriate type of Info. + std::unique_ptr readBlockToInfo(unsigned ID); + + llvm::BitstreamCursor &Stream; + Optional BlockInfo; + FieldId CurrentReferenceField; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEREADER_H diff --git a/clang-doc/BitcodeWriter.cpp b/clang-doc/BitcodeWriter.cpp new file mode 100644 index 000000000..623ed1a2a --- /dev/null +++ b/clang-doc/BitcodeWriter.cpp @@ -0,0 +1,518 @@ +//===-- BitcodeWriter.cpp - ClangDoc Bitcode Writer ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BitcodeWriter.h" +#include "llvm/ADT/IndexedMap.h" +#include + +namespace clang { +namespace doc { + +// Empty SymbolID for comparison, so we don't have to construct one every time. +static const SymbolID EmptySID = SymbolID(); + +// Since id enums are not zero-indexed, we need to transform the given id into +// its associated index. +struct BlockIdToIndexFunctor { + using argument_type = unsigned; + unsigned operator()(unsigned ID) const { return ID - BI_FIRST; } +}; + +struct RecordIdToIndexFunctor { + using argument_type = unsigned; + unsigned operator()(unsigned ID) const { return ID - RI_FIRST; } +}; + +using AbbrevDsc = void (*)(std::shared_ptr &Abbrev); + +static void AbbrevGen(std::shared_ptr &Abbrev, + const std::initializer_list Ops) { + for (const auto &Op : Ops) + Abbrev->Add(Op); +} + +static void BoolAbbrev(std::shared_ptr &Abbrev) { + AbbrevGen(Abbrev, + {// 0. Boolean + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::BoolSize)}); +} + +static void IntAbbrev(std::shared_ptr &Abbrev) { + AbbrevGen(Abbrev, + {// 0. Fixed-size integer + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::IntSize)}); +} + +static void SymbolIDAbbrev(std::shared_ptr &Abbrev) { + AbbrevGen(Abbrev, + {// 0. Fixed-size integer (length of the sha1'd USR) + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::USRLengthSize), + // 1. Fixed-size array of Char6 (USR) + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Array), + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::USRBitLengthSize)}); +} + +static void StringAbbrev(std::shared_ptr &Abbrev) { + AbbrevGen(Abbrev, + {// 0. Fixed-size integer (length of the following string) + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::StringLengthSize), + // 1. The string blob + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)}); +} + +// Assumes that the file will not have more than 65535 lines. +static void LocationAbbrev(std::shared_ptr &Abbrev) { + AbbrevGen( + Abbrev, + {// 0. Fixed-size integer (line number) + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::LineNumberSize), + // 1. Fixed-size integer (length of the following string (filename)) + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::StringLengthSize), + // 2. The string blob + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)}); +} + +struct RecordIdDsc { + llvm::StringRef Name; + AbbrevDsc Abbrev = nullptr; + + RecordIdDsc() = default; + RecordIdDsc(llvm::StringRef Name, AbbrevDsc Abbrev) + : Name(Name), Abbrev(Abbrev) {} + + // Is this 'description' valid? + operator bool() const { + return Abbrev != nullptr && Name.data() != nullptr && !Name.empty(); + } +}; + +static const llvm::IndexedMap + BlockIdNameMap = []() { + llvm::IndexedMap BlockIdNameMap; + BlockIdNameMap.resize(BlockIdCount); + + // There is no init-list constructor for the IndexedMap, so have to + // improvise + static const std::vector> Inits = { + {BI_VERSION_BLOCK_ID, "VersionBlock"}, + {BI_NAMESPACE_BLOCK_ID, "NamespaceBlock"}, + {BI_ENUM_BLOCK_ID, "EnumBlock"}, + {BI_TYPE_BLOCK_ID, "TypeBlock"}, + {BI_FIELD_TYPE_BLOCK_ID, "FieldTypeBlock"}, + {BI_MEMBER_TYPE_BLOCK_ID, "MemberTypeBlock"}, + {BI_RECORD_BLOCK_ID, "RecordBlock"}, + {BI_FUNCTION_BLOCK_ID, "FunctionBlock"}, + {BI_COMMENT_BLOCK_ID, "CommentBlock"}, + {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"}}; + assert(Inits.size() == BlockIdCount); + for (const auto &Init : Inits) + BlockIdNameMap[Init.first] = Init.second; + assert(BlockIdNameMap.size() == BlockIdCount); + return BlockIdNameMap; + }(); + +static const llvm::IndexedMap + RecordIdNameMap = []() { + llvm::IndexedMap RecordIdNameMap; + RecordIdNameMap.resize(RecordIdCount); + + // There is no init-list constructor for the IndexedMap, so have to + // improvise + static const std::vector> Inits = { + {VERSION, {"Version", &IntAbbrev}}, + {COMMENT_KIND, {"Kind", &StringAbbrev}}, + {COMMENT_TEXT, {"Text", &StringAbbrev}}, + {COMMENT_NAME, {"Name", &StringAbbrev}}, + {COMMENT_DIRECTION, {"Direction", &StringAbbrev}}, + {COMMENT_PARAMNAME, {"ParamName", &StringAbbrev}}, + {COMMENT_CLOSENAME, {"CloseName", &StringAbbrev}}, + {COMMENT_SELFCLOSING, {"SelfClosing", &BoolAbbrev}}, + {COMMENT_EXPLICIT, {"Explicit", &BoolAbbrev}}, + {COMMENT_ATTRKEY, {"AttrKey", &StringAbbrev}}, + {COMMENT_ATTRVAL, {"AttrVal", &StringAbbrev}}, + {COMMENT_ARG, {"Arg", &StringAbbrev}}, + {FIELD_TYPE_NAME, {"Name", &StringAbbrev}}, + {MEMBER_TYPE_NAME, {"Name", &StringAbbrev}}, + {MEMBER_TYPE_ACCESS, {"Access", &IntAbbrev}}, + {NAMESPACE_USR, {"USR", &SymbolIDAbbrev}}, + {NAMESPACE_NAME, {"Name", &StringAbbrev}}, + {ENUM_USR, {"USR", &SymbolIDAbbrev}}, + {ENUM_NAME, {"Name", &StringAbbrev}}, + {ENUM_DEFLOCATION, {"DefLocation", &LocationAbbrev}}, + {ENUM_LOCATION, {"Location", &LocationAbbrev}}, + {ENUM_MEMBER, {"Member", &StringAbbrev}}, + {ENUM_SCOPED, {"Scoped", &BoolAbbrev}}, + {RECORD_USR, {"USR", &SymbolIDAbbrev}}, + {RECORD_NAME, {"Name", &StringAbbrev}}, + {RECORD_DEFLOCATION, {"DefLocation", &LocationAbbrev}}, + {RECORD_LOCATION, {"Location", &LocationAbbrev}}, + {RECORD_TAG_TYPE, {"TagType", &IntAbbrev}}, + {FUNCTION_USR, {"USR", &SymbolIDAbbrev}}, + {FUNCTION_NAME, {"Name", &StringAbbrev}}, + {FUNCTION_DEFLOCATION, {"DefLocation", &LocationAbbrev}}, + {FUNCTION_LOCATION, {"Location", &LocationAbbrev}}, + {FUNCTION_ACCESS, {"Access", &IntAbbrev}}, + {FUNCTION_IS_METHOD, {"IsMethod", &BoolAbbrev}}, + {REFERENCE_USR, {"USR", &SymbolIDAbbrev}}, + {REFERENCE_NAME, {"Name", &StringAbbrev}}, + {REFERENCE_TYPE, {"RefType", &IntAbbrev}}, + {REFERENCE_FIELD, {"Field", &IntAbbrev}}}; + assert(Inits.size() == RecordIdCount); + for (const auto &Init : Inits) { + RecordIdNameMap[Init.first] = Init.second; + assert((Init.second.Name.size() + 1) <= BitCodeConstants::RecordSize); + } + assert(RecordIdNameMap.size() == RecordIdCount); + return RecordIdNameMap; + }(); + +static const std::vector>> + RecordsByBlock{ + // Version Block + {BI_VERSION_BLOCK_ID, {VERSION}}, + // Comment Block + {BI_COMMENT_BLOCK_ID, + {COMMENT_KIND, COMMENT_TEXT, COMMENT_NAME, COMMENT_DIRECTION, + COMMENT_PARAMNAME, COMMENT_CLOSENAME, COMMENT_SELFCLOSING, + COMMENT_EXPLICIT, COMMENT_ATTRKEY, COMMENT_ATTRVAL, COMMENT_ARG}}, + // Type Block + {BI_TYPE_BLOCK_ID, {}}, + // FieldType Block + {BI_FIELD_TYPE_BLOCK_ID, {FIELD_TYPE_NAME}}, + // MemberType Block + {BI_MEMBER_TYPE_BLOCK_ID, {MEMBER_TYPE_NAME, MEMBER_TYPE_ACCESS}}, + // Enum Block + {BI_ENUM_BLOCK_ID, + {ENUM_USR, ENUM_NAME, ENUM_DEFLOCATION, ENUM_LOCATION, ENUM_MEMBER, + ENUM_SCOPED}}, + // Namespace Block + {BI_NAMESPACE_BLOCK_ID, {NAMESPACE_USR, NAMESPACE_NAME}}, + // Record Block + {BI_RECORD_BLOCK_ID, + {RECORD_USR, RECORD_NAME, RECORD_DEFLOCATION, RECORD_LOCATION, + RECORD_TAG_TYPE}}, + // Function Block + {BI_FUNCTION_BLOCK_ID, + {FUNCTION_USR, FUNCTION_NAME, FUNCTION_DEFLOCATION, FUNCTION_LOCATION, + FUNCTION_ACCESS, FUNCTION_IS_METHOD}}, + // Reference Block + {BI_REFERENCE_BLOCK_ID, + {REFERENCE_USR, REFERENCE_NAME, REFERENCE_TYPE, REFERENCE_FIELD}}}; + +// AbbreviationMap + +constexpr char BitCodeConstants::Signature[]; + +void ClangDocBitcodeWriter::AbbreviationMap::add(RecordId RID, + unsigned AbbrevID) { + assert(RecordIdNameMap[RID] && "Unknown RecordId."); + assert(Abbrevs.find(RID) == Abbrevs.end() && "Abbreviation already added."); + Abbrevs[RID] = AbbrevID; +} + +unsigned ClangDocBitcodeWriter::AbbreviationMap::get(RecordId RID) const { + assert(RecordIdNameMap[RID] && "Unknown RecordId."); + assert(Abbrevs.find(RID) != Abbrevs.end() && "Unknown abbreviation."); + return Abbrevs.lookup(RID); +} + +// Validation and Overview Blocks + +/// \brief Emits the magic number header to check that its the right format, +/// in this case, 'DOCS'. +void ClangDocBitcodeWriter::emitHeader() { + for (char C : BitCodeConstants::Signature) + Stream.Emit((unsigned)C, BitCodeConstants::SignatureBitSize); +} + +void ClangDocBitcodeWriter::emitVersionBlock() { + StreamSubBlockGuard Block(Stream, BI_VERSION_BLOCK_ID); + emitRecord(VersionNumber, VERSION); +} + +/// \brief Emits a block ID and the block name to the BLOCKINFO block. +void ClangDocBitcodeWriter::emitBlockID(BlockId BID) { + const auto &BlockIdName = BlockIdNameMap[BID]; + assert(BlockIdName.data() && BlockIdName.size() && "Unknown BlockId."); + + Record.clear(); + Record.push_back(BID); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, Record); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, + ArrayRef(BlockIdName.bytes_begin(), + BlockIdName.bytes_end())); +} + +/// \brief Emits a record name to the BLOCKINFO block. +void ClangDocBitcodeWriter::emitRecordID(RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + prepRecordData(ID); + Record.append(RecordIdNameMap[ID].Name.begin(), + RecordIdNameMap[ID].Name.end()); + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Record); +} + +// Abbreviations + +void ClangDocBitcodeWriter::emitAbbrev(RecordId ID, BlockId Block) { + assert(RecordIdNameMap[ID] && "Unknown abbreviation."); + auto Abbrev = std::make_shared(); + Abbrev->Add(llvm::BitCodeAbbrevOp(ID)); + RecordIdNameMap[ID].Abbrev(Abbrev); + Abbrevs.add(ID, Stream.EmitBlockInfoAbbrev(Block, std::move(Abbrev))); +} + +// Records + +void ClangDocBitcodeWriter::emitRecord(const SymbolID &Sym, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &SymbolIDAbbrev && + "Abbrev type mismatch."); + if (!prepRecordData(ID, Sym != EmptySID)) + return; + assert(Sym.size() == 20); + Record.push_back(Sym.size()); + Record.append(Sym.begin(), Sym.end()); + Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); +} + +void ClangDocBitcodeWriter::emitRecord(llvm::StringRef Str, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &StringAbbrev && + "Abbrev type mismatch."); + if (!prepRecordData(ID, !Str.empty())) + return; + assert(Str.size() < (1U << BitCodeConstants::StringLengthSize)); + Record.push_back(Str.size()); + Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Str); +} + +void ClangDocBitcodeWriter::emitRecord(const Location &Loc, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &LocationAbbrev && + "Abbrev type mismatch."); + if (!prepRecordData(ID, true)) + return; + // FIXME: Assert that the line number is of the appropriate size. + Record.push_back(Loc.LineNumber); + assert(Loc.Filename.size() < (1U << BitCodeConstants::StringLengthSize)); + // Record.push_back(Loc.Filename.size()); + // Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, Loc.Filename); + Record.push_back(4); + Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, "test"); +} + +void ClangDocBitcodeWriter::emitRecord(bool Val, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &BoolAbbrev && "Abbrev type mismatch."); + if (!prepRecordData(ID, Val)) + return; + Record.push_back(Val); + Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); +} + +void ClangDocBitcodeWriter::emitRecord(int Val, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &IntAbbrev && "Abbrev type mismatch."); + if (!prepRecordData(ID, Val)) + return; + // FIXME: Assert that the integer is of the appropriate size. + Record.push_back(Val); + Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); +} + +void ClangDocBitcodeWriter::emitRecord(unsigned Val, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &IntAbbrev && "Abbrev type mismatch."); + if (!prepRecordData(ID, Val)) + return; + assert(Val < (1U << BitCodeConstants::IntSize)); + Record.push_back(Val); + Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); +} + +bool ClangDocBitcodeWriter::prepRecordData(RecordId ID, bool ShouldEmit) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + if (!ShouldEmit) + return false; + Record.clear(); + Record.push_back(ID); + return true; +} + +// BlockInfo Block + +void ClangDocBitcodeWriter::emitBlockInfoBlock() { + Stream.EnterBlockInfoBlock(); + for (const auto &Block : RecordsByBlock) { + assert(Block.second.size() < (1U << BitCodeConstants::SubblockIDSize)); + emitBlockInfo(Block.first, Block.second); + } + Stream.ExitBlock(); +} + +void ClangDocBitcodeWriter::emitBlockInfo(BlockId BID, + const std::vector &RIDs) { + assert(RIDs.size() < (1U << BitCodeConstants::SubblockIDSize)); + emitBlockID(BID); + for (RecordId RID : RIDs) { + emitRecordID(RID); + emitAbbrev(RID, BID); + } +} + +// Block emission + +void ClangDocBitcodeWriter::emitBlock(const Reference &R, FieldId Field) { + if (R.USR == EmptySID && R.Name.empty()) + return; + StreamSubBlockGuard Block(Stream, BI_REFERENCE_BLOCK_ID); + emitRecord(R.USR, REFERENCE_USR); + emitRecord(R.Name, REFERENCE_NAME); + emitRecord((unsigned)R.RefType, REFERENCE_TYPE); + emitRecord((unsigned)Field, REFERENCE_FIELD); +} + +void ClangDocBitcodeWriter::emitBlock(const TypeInfo &T) { + StreamSubBlockGuard Block(Stream, BI_TYPE_BLOCK_ID); + emitBlock(T.Type, FieldId::F_type); +} + +void ClangDocBitcodeWriter::emitBlock(const FieldTypeInfo &T) { + StreamSubBlockGuard Block(Stream, BI_FIELD_TYPE_BLOCK_ID); + emitBlock(T.Type, FieldId::F_type); + emitRecord(T.Name, FIELD_TYPE_NAME); +} + +void ClangDocBitcodeWriter::emitBlock(const MemberTypeInfo &T) { + StreamSubBlockGuard Block(Stream, BI_MEMBER_TYPE_BLOCK_ID); + emitBlock(T.Type, FieldId::F_type); + emitRecord(T.Name, MEMBER_TYPE_NAME); + emitRecord(T.Access, MEMBER_TYPE_ACCESS); +} + +void ClangDocBitcodeWriter::emitBlock(const CommentInfo &I) { + StreamSubBlockGuard Block(Stream, BI_COMMENT_BLOCK_ID); + for (const auto &L : std::vector>{ + {I.Kind, COMMENT_KIND}, + {I.Text, COMMENT_TEXT}, + {I.Name, COMMENT_NAME}, + {I.Direction, COMMENT_DIRECTION}, + {I.ParamName, COMMENT_PARAMNAME}, + {I.CloseName, COMMENT_CLOSENAME}}) + emitRecord(L.first, L.second); + emitRecord(I.SelfClosing, COMMENT_SELFCLOSING); + emitRecord(I.Explicit, COMMENT_EXPLICIT); + for (const auto &A : I.AttrKeys) + emitRecord(A, COMMENT_ATTRKEY); + for (const auto &A : I.AttrValues) + emitRecord(A, COMMENT_ATTRVAL); + for (const auto &A : I.Args) + emitRecord(A, COMMENT_ARG); + for (const auto &C : I.Children) + emitBlock(*C); +} + +void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) { + StreamSubBlockGuard Block(Stream, BI_NAMESPACE_BLOCK_ID); + emitRecord(I.USR, NAMESPACE_USR); + emitRecord(I.Name, NAMESPACE_NAME); + for (const auto &N : I.Namespace) + emitBlock(N, FieldId::F_namespace); + for (const auto &CI : I.Description) + emitBlock(CI); +} + +void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) { + StreamSubBlockGuard Block(Stream, BI_ENUM_BLOCK_ID); + emitRecord(I.USR, ENUM_USR); + emitRecord(I.Name, ENUM_NAME); + for (const auto &N : I.Namespace) + emitBlock(N, FieldId::F_namespace); + for (const auto &CI : I.Description) + emitBlock(CI); + if (I.DefLoc) + emitRecord(I.DefLoc.getValue(), ENUM_DEFLOCATION); + for (const auto &L : I.Loc) + emitRecord(L, ENUM_LOCATION); + emitRecord(I.Scoped, ENUM_SCOPED); + for (const auto &N : I.Members) + emitRecord(N, ENUM_MEMBER); +} + +void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) { + StreamSubBlockGuard Block(Stream, BI_RECORD_BLOCK_ID); + emitRecord(I.USR, RECORD_USR); + emitRecord(I.Name, RECORD_NAME); + for (const auto &N : I.Namespace) + emitBlock(N, FieldId::F_namespace); + for (const auto &CI : I.Description) + emitBlock(CI); + if (I.DefLoc) + emitRecord(I.DefLoc.getValue(), RECORD_DEFLOCATION); + for (const auto &L : I.Loc) + emitRecord(L, RECORD_LOCATION); + emitRecord(I.TagType, RECORD_TAG_TYPE); + for (const auto &N : I.Members) + emitBlock(N); + for (const auto &P : I.Parents) + emitBlock(P, FieldId::F_parent); + for (const auto &P : I.VirtualParents) + emitBlock(P, FieldId::F_vparent); +} + +void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) { + StreamSubBlockGuard Block(Stream, BI_FUNCTION_BLOCK_ID); + emitRecord(I.USR, FUNCTION_USR); + emitRecord(I.Name, FUNCTION_NAME); + for (const auto &N : I.Namespace) + emitBlock(N, FieldId::F_namespace); + for (const auto &CI : I.Description) + emitBlock(CI); + emitRecord(I.IsMethod, FUNCTION_IS_METHOD); + if (I.DefLoc) + emitRecord(I.DefLoc.getValue(), FUNCTION_DEFLOCATION); + for (const auto &L : I.Loc) + emitRecord(L, FUNCTION_LOCATION); + emitBlock(I.Parent, FieldId::F_parent); + emitBlock(I.ReturnType); + for (const auto &N : I.Params) + emitBlock(N); +} + +bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { + switch (I->IT) { + case InfoType::IT_namespace: + emitBlock(*static_cast(I)); + break; + case InfoType::IT_record: + emitBlock(*static_cast(I)); + break; + case InfoType::IT_enum: + emitBlock(*static_cast(I)); + break; + case InfoType::IT_function: + emitBlock(*static_cast(I)); + break; + default: + llvm::errs() << "Unexpected info, unable to write.\n"; + return true; + } + return false; +} + +} // namespace doc +} // namespace clang diff --git a/clang-doc/BitcodeWriter.h b/clang-doc/BitcodeWriter.h new file mode 100644 index 000000000..3d8a68fbc --- /dev/null +++ b/clang-doc/BitcodeWriter.h @@ -0,0 +1,201 @@ +//===-- BitcodeWriter.h - ClangDoc Bitcode Writer --------------*- C++ -*-===// +// +// 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 writer for serializing the clang-doc internal +// representation to LLVM bitcode. The writer takes in a stream and emits the +// generated bitcode to that stream. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H + +#include "Representation.h" +#include "clang/AST/AST.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Bitcode/BitstreamWriter.h" +#include +#include + +namespace clang { +namespace doc { + +// Current version number of clang-doc bitcode. +// Should be bumped when removing or changing BlockIds, RecordIds, or +// BitCodeConstants, though they can be added without breaking it. +static const unsigned VersionNumber = 2; + +struct BitCodeConstants { + static constexpr unsigned RecordSize = 32U; + static constexpr unsigned SignatureBitSize = 8U; + static constexpr unsigned SubblockIDSize = 4U; + static constexpr unsigned BoolSize = 1U; + static constexpr unsigned IntSize = 16U; + static constexpr unsigned StringLengthSize = 16U; + static constexpr unsigned FilenameLengthSize = 16U; + static constexpr unsigned LineNumberSize = 16U; + static constexpr unsigned ReferenceTypeSize = 8U; + static constexpr unsigned USRLengthSize = 6U; + static constexpr unsigned USRBitLengthSize = 8U; + static constexpr char Signature[4] = {'D', 'O', 'C', 'S'}; + static constexpr int USRHashSize = 20; +}; + +// New Ids need to be added to both the enum here and the relevant IdNameMap in +// the implementation file. +enum BlockId { + BI_VERSION_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID, + BI_NAMESPACE_BLOCK_ID, + BI_ENUM_BLOCK_ID, + BI_TYPE_BLOCK_ID, + BI_FIELD_TYPE_BLOCK_ID, + BI_MEMBER_TYPE_BLOCK_ID, + BI_RECORD_BLOCK_ID, + BI_FUNCTION_BLOCK_ID, + BI_COMMENT_BLOCK_ID, + BI_REFERENCE_BLOCK_ID, + BI_LAST, + BI_FIRST = BI_VERSION_BLOCK_ID +}; + +// New Ids need to be added to the enum here, and to the relevant IdNameMap and +// initialization list in the implementation file. +#define INFORECORDS(X) X##_USR, X##_NAME + +enum RecordId { + VERSION = 1, + INFORECORDS(FUNCTION), + FUNCTION_DEFLOCATION, + FUNCTION_LOCATION, + FUNCTION_ACCESS, + FUNCTION_IS_METHOD, + COMMENT_KIND, + COMMENT_TEXT, + COMMENT_NAME, + COMMENT_DIRECTION, + COMMENT_PARAMNAME, + COMMENT_CLOSENAME, + COMMENT_SELFCLOSING, + COMMENT_EXPLICIT, + COMMENT_ATTRKEY, + COMMENT_ATTRVAL, + COMMENT_ARG, + FIELD_TYPE_NAME, + MEMBER_TYPE_NAME, + MEMBER_TYPE_ACCESS, + INFORECORDS(NAMESPACE), + INFORECORDS(ENUM), + ENUM_DEFLOCATION, + ENUM_LOCATION, + ENUM_MEMBER, + ENUM_SCOPED, + INFORECORDS(RECORD), + RECORD_DEFLOCATION, + RECORD_LOCATION, + RECORD_TAG_TYPE, + REFERENCE_USR, + REFERENCE_NAME, + REFERENCE_TYPE, + REFERENCE_FIELD, + RI_LAST, + RI_FIRST = VERSION +}; + +static constexpr unsigned BlockIdCount = BI_LAST - BI_FIRST; +static constexpr unsigned RecordIdCount = RI_LAST - RI_FIRST; + +#undef INFORECORDS + +// Identifiers for differentiating between subblocks +enum class FieldId { F_default, F_namespace, F_parent, F_vparent, F_type }; + +class ClangDocBitcodeWriter { +public: + ClangDocBitcodeWriter(llvm::BitstreamWriter &Stream) : Stream(Stream) { + emitHeader(); + emitBlockInfoBlock(); + emitVersionBlock(); + } + + // Write a specific info to a bitcode stream. + bool dispatchInfoForWrite(Info *I); + + // Block emission of different info types. + void emitBlock(const NamespaceInfo &I); + void emitBlock(const RecordInfo &I); + void emitBlock(const FunctionInfo &I); + void emitBlock(const EnumInfo &I); + void emitBlock(const TypeInfo &B); + void emitBlock(const FieldTypeInfo &B); + void emitBlock(const MemberTypeInfo &B); + void emitBlock(const CommentInfo &B); + void emitBlock(const Reference &B, FieldId F); + +private: + class AbbreviationMap { + llvm::DenseMap Abbrevs; + + public: + AbbreviationMap() : Abbrevs(RecordIdCount) {} + + void add(RecordId RID, unsigned AbbrevID); + unsigned get(RecordId RID) const; + }; + + class StreamSubBlockGuard { + llvm::BitstreamWriter &Stream; + + public: + StreamSubBlockGuard(llvm::BitstreamWriter &Stream_, BlockId ID) + : Stream(Stream_) { + // NOTE: SubBlockIDSize could theoretically be calculated on the fly, + // based on the initialization list of records in each block. + Stream.EnterSubblock(ID, BitCodeConstants::SubblockIDSize); + } + + StreamSubBlockGuard(const StreamSubBlockGuard &) = delete; + StreamSubBlockGuard &operator=(const StreamSubBlockGuard &) = delete; + + ~StreamSubBlockGuard() { Stream.ExitBlock(); } + }; + + // Emission of validation and overview blocks. + void emitHeader(); + void emitVersionBlock(); + void emitRecordID(RecordId ID); + void emitBlockID(BlockId ID); + void emitBlockInfoBlock(); + void emitBlockInfo(BlockId BID, const std::vector &RIDs); + + // Emission of individual record types. + void emitRecord(StringRef Str, RecordId ID); + void emitRecord(const SymbolID &Str, RecordId ID); + void emitRecord(const Location &Loc, RecordId ID); + void emitRecord(const Reference &Ref, RecordId ID); + void emitRecord(bool Value, RecordId ID); + void emitRecord(int Value, RecordId ID); + void emitRecord(unsigned Value, RecordId ID); + bool prepRecordData(RecordId ID, bool ShouldEmit = true); + + // Emission of appropriate abbreviation type. + void emitAbbrev(RecordId ID, BlockId Block); + + // Static size is the maximum length of the block/record names we're pushing + // to this + 1. Longest is currently `MemberTypeBlock` at 15 chars. + SmallVector Record; + llvm::BitstreamWriter &Stream; + AbbreviationMap Abbrevs; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H diff --git a/clang-doc/CMakeLists.txt b/clang-doc/CMakeLists.txt new file mode 100644 index 000000000..eaebf616f --- /dev/null +++ b/clang-doc/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS + support + BitReader + BitWriter + ) + +add_clang_library(clangDoc + BitcodeReader.cpp + BitcodeWriter.cpp + ClangDoc.cpp + Generators.cpp + Mapper.cpp + Representation.cpp + Serialize.cpp + YAMLGenerator.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangIndex + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-doc/ClangDoc.cpp b/clang-doc/ClangDoc.cpp new file mode 100644 index 000000000..a11c58400 --- /dev/null +++ b/clang-doc/ClangDoc.cpp @@ -0,0 +1,62 @@ +//===-- ClangDoc.cpp - ClangDoc ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the main entry point for the clang-doc tool. It runs +// the clang-doc mapper on a given set of source code files using a +// FrontendActionFactory. +// +//===----------------------------------------------------------------------===// + +#include "ClangDoc.h" +#include "Mapper.h" +#include "Representation.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" + +namespace clang { +namespace doc { + +class MapperActionFactory : public tooling::FrontendActionFactory { +public: + MapperActionFactory(ClangDocContext CDCtx) : CDCtx(CDCtx) {} + clang::FrontendAction *create() override; + +private: + ClangDocContext CDCtx; +}; + +clang::FrontendAction *MapperActionFactory::create() { + class ClangDocAction : public clang::ASTFrontendAction { + public: + ClangDocAction(ClangDocContext CDCtx) : CDCtx(CDCtx) {} + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef InFile) override { + return llvm::make_unique(&Compiler.getASTContext(), CDCtx); + } + + private: + ClangDocContext CDCtx; + }; + return new ClangDocAction(CDCtx); +} + +std::unique_ptr +newMapperActionFactory(ClangDocContext CDCtx) { + return llvm::make_unique(CDCtx); +} + +} // namespace doc +} // namespace clang diff --git a/clang-doc/ClangDoc.h b/clang-doc/ClangDoc.h new file mode 100644 index 000000000..59e9a92b1 --- /dev/null +++ b/clang-doc/ClangDoc.h @@ -0,0 +1,34 @@ +//===-- ClangDoc.h - ClangDoc -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file exposes a method to craete the FrontendActionFactory for the +// clang-doc tool. The factory runs the clang-doc mapper on a given set of +// source code files, storing the results key-value pairs in its +// ExecutionContext. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H + +#include "Representation.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/StandaloneExecution.h" +#include "clang/Tooling/Tooling.h" + +namespace clang { +namespace doc { + +std::unique_ptr +newMapperActionFactory(ClangDocContext CDCtx); + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANGDOC_H diff --git a/clang-doc/Generators.cpp b/clang-doc/Generators.cpp new file mode 100644 index 000000000..fe01d6109 --- /dev/null +++ b/clang-doc/Generators.cpp @@ -0,0 +1,36 @@ +//===---- Generator.cpp - Generator Registry ---------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Generators.h" + +LLVM_INSTANTIATE_REGISTRY(clang::doc::GeneratorRegistry) + +namespace clang { +namespace doc { + +llvm::Expected> +findGeneratorByName(llvm::StringRef Format) { + for (auto I = GeneratorRegistry::begin(), E = GeneratorRegistry::end(); + I != E; ++I) { + if (I->getName() != Format) + continue; + return I->instantiate(); + } + return llvm::make_error("Can't find generator: " + Format, + llvm::inconvertibleErrorCode()); +} + +// This anchor is used to force the linker to link in the generated object file +// and thus register the generators. +extern volatile int YAMLGeneratorAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest = + YAMLGeneratorAnchorSource; + +} // namespace doc +} // namespace clang diff --git a/clang-doc/Generators.h b/clang-doc/Generators.h new file mode 100644 index 000000000..9106d2cff --- /dev/null +++ b/clang-doc/Generators.h @@ -0,0 +1,41 @@ +//===-- Generators.h - ClangDoc Generator ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Generator classes for converting declaration information into documentation +// in a specified format. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_GENERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_GENERATOR_H + +#include "Representation.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace doc { + +// Abstract base class for generators. +// This is expected to be implemented and exposed via the GeneratorRegistry. +class Generator { +public: + virtual ~Generator() = default; + + // Write out the decl info in the specified format. + virtual bool generateDocForInfo(Info *I, llvm::raw_ostream &OS) = 0; +}; + +typedef llvm::Registry GeneratorRegistry; + +llvm::Expected> +findGeneratorByName(llvm::StringRef Format); + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_GENERATOR_H diff --git a/clang-doc/Mapper.cpp b/clang-doc/Mapper.cpp new file mode 100644 index 000000000..fb0b42af3 --- /dev/null +++ b/clang-doc/Mapper.cpp @@ -0,0 +1,90 @@ +//===-- Mapper.cpp - ClangDoc Mapper ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Mapper.h" +#include "BitcodeWriter.h" +#include "Serialize.h" +#include "clang/AST/Comment.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/ADT/StringExtras.h" + +using clang::comments::FullComment; + +namespace clang { +namespace doc { + +void MapASTVisitor::HandleTranslationUnit(ASTContext &Context) { + TraverseDecl(Context.getTranslationUnitDecl()); +} + +template bool MapASTVisitor::mapDecl(const T *D) { + // If we're looking a decl not in user files, skip this decl. + if (D->getASTContext().getSourceManager().isInSystemHeader(D->getLocation())) + return true; + + llvm::SmallString<128> USR; + // If there is an error generating a USR for the decl, skip this decl. + if (index::generateUSRForDecl(D, USR)) + return true; + + std::string info = serialize::emitInfo( + D, getComment(D, D->getASTContext()), getLine(D, D->getASTContext()), + getFile(D, D->getASTContext()), CDCtx.PublicOnly); + + if (info != "") + CDCtx.ECtx->reportResult( + llvm::toHex(llvm::toStringRef(serialize::hashUSR(USR))), info); + + return true; +} + +bool MapASTVisitor::VisitNamespaceDecl(const NamespaceDecl *D) { + return mapDecl(D); +} + +bool MapASTVisitor::VisitRecordDecl(const RecordDecl *D) { return mapDecl(D); } + +bool MapASTVisitor::VisitEnumDecl(const EnumDecl *D) { return mapDecl(D); } + +bool MapASTVisitor::VisitCXXMethodDecl(const CXXMethodDecl *D) { + return mapDecl(D); +} + +bool MapASTVisitor::VisitFunctionDecl(const FunctionDecl *D) { + // Don't visit CXXMethodDecls twice + if (dyn_cast(D)) + return true; + return mapDecl(D); +} + +comments::FullComment * +MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const { + RawComment *Comment = Context.getRawCommentForDeclNoCache(D); + // FIXME: Move setAttached to the initial comment parsing. + if (Comment) { + Comment->setAttached(); + return Comment->parse(Context, nullptr, D); + } + return nullptr; +} + +int MapASTVisitor::getLine(const NamedDecl *D, + const ASTContext &Context) const { + return Context.getSourceManager().getPresumedLoc(D->getLocStart()).getLine(); +} + +llvm::StringRef MapASTVisitor::getFile(const NamedDecl *D, + const ASTContext &Context) const { + return Context.getSourceManager() + .getPresumedLoc(D->getLocStart()) + .getFilename(); +} + +} // namespace doc +} // namespace clang diff --git a/clang-doc/Mapper.h b/clang-doc/Mapper.h new file mode 100644 index 000000000..a0b1ac22c --- /dev/null +++ b/clang-doc/Mapper.h @@ -0,0 +1,58 @@ +//===-- Mapper.h - ClangDoc Mapper ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the Mapper piece of the clang-doc tool. It implements +// a RecursiveASTVisitor to look at each declaration and populate the info +// into the internal representation. Each seen declaration is serialized to +// to bitcode and written out to the ExecutionContext as a KV pair where the +// key is the declaration's USR and the value is the serialized bitcode. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H + +#include "Representation.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Tooling/Execution.h" + +using namespace clang::comments; +using namespace clang::tooling; + +namespace clang { +namespace doc { + +class MapASTVisitor : public clang::RecursiveASTVisitor, + public ASTConsumer { +public: + explicit MapASTVisitor(ASTContext *Ctx, ClangDocContext CDCtx) + : CDCtx(CDCtx) {} + + void HandleTranslationUnit(ASTContext &Context) override; + bool VisitNamespaceDecl(const NamespaceDecl *D); + bool VisitRecordDecl(const RecordDecl *D); + bool VisitEnumDecl(const EnumDecl *D); + bool VisitCXXMethodDecl(const CXXMethodDecl *D); + bool VisitFunctionDecl(const FunctionDecl *D); + +private: + template bool mapDecl(const T *D); + + int getLine(const NamedDecl *D, const ASTContext &Context) const; + StringRef getFile(const NamedDecl *D, const ASTContext &Context) const; + comments::FullComment *getComment(const NamedDecl *D, + const ASTContext &Context) const; + + ClangDocContext CDCtx; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_MAPPER_H diff --git a/clang-doc/Representation.cpp b/clang-doc/Representation.cpp new file mode 100644 index 000000000..6107b98ec --- /dev/null +++ b/clang-doc/Representation.cpp @@ -0,0 +1,131 @@ +///===-- Representation.cpp - ClangDoc Representation -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the merging of different types of infos. The data in the +// calling Info is preserved during a merge unless that field is empty or +// default. In that case, the data from the parameter Info is used to replace +// the empty or default data. +// +// For most fields, the first decl seen provides the data. Exceptions to this +// include the location and description fields, which are collections of data on +// all decls related to a given definition. All other fields are ignored in new +// decls unless the first seen decl didn't, for whatever reason, incorporate +// data on that field (e.g. a forward declared class wouldn't have information +// on members on the forward declaration, but would have the class name). +// +//===----------------------------------------------------------------------===// +#include "Representation.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace doc { + +static const SymbolID EmptySID = SymbolID(); + +template +std::unique_ptr reduce(std::vector> &Values) { + std::unique_ptr Merged = llvm::make_unique(); + T *Tmp = static_cast(Merged.get()); + for (auto &I : Values) + Tmp->merge(std::move(*static_cast(I.get()))); + return Merged; +} + +// Dispatch function. +llvm::Expected> +mergeInfos(std::vector> &Values) { + if (Values.empty()) + return llvm::make_error("No info values to merge.\n", + llvm::inconvertibleErrorCode()); + + switch (Values[0]->IT) { + case InfoType::IT_namespace: + return reduce(Values); + case InfoType::IT_record: + return reduce(Values); + case InfoType::IT_enum: + return reduce(Values); + case InfoType::IT_function: + return reduce(Values); + default: + return llvm::make_error("Unexpected info type.\n", + llvm::inconvertibleErrorCode()); + } +} + +void Info::mergeBase(Info &&Other) { + assert(mergeable(Other)); + if (USR == EmptySID) + USR = Other.USR; + if (Name == "") + Name = Other.Name; + if (Namespace.empty()) + Namespace = std::move(Other.Namespace); + // Unconditionally extend the description, since each decl may have a comment. + std::move(Other.Description.begin(), Other.Description.end(), + std::back_inserter(Description)); +} + +bool Info::mergeable(const Info &Other) { + return IT == Other.IT && (USR == EmptySID || USR == Other.USR); +} + +void SymbolInfo::merge(SymbolInfo &&Other) { + assert(mergeable(Other)); + if (!DefLoc) + DefLoc = std::move(Other.DefLoc); + // Unconditionally extend the list of locations, since we want all of them. + std::move(Other.Loc.begin(), Other.Loc.end(), std::back_inserter(Loc)); + mergeBase(std::move(Other)); +} + +void NamespaceInfo::merge(NamespaceInfo &&Other) { + assert(mergeable(Other)); + mergeBase(std::move(Other)); +} + +void RecordInfo::merge(RecordInfo &&Other) { + assert(mergeable(Other)); + if (!TagType) + TagType = Other.TagType; + if (Members.empty()) + Members = std::move(Other.Members); + if (Parents.empty()) + Parents = std::move(Other.Parents); + if (VirtualParents.empty()) + VirtualParents = std::move(Other.VirtualParents); + SymbolInfo::merge(std::move(Other)); +} + +void EnumInfo::merge(EnumInfo &&Other) { + assert(mergeable(Other)); + if (!Scoped) + Scoped = Other.Scoped; + if (Members.empty()) + Members = std::move(Other.Members); + SymbolInfo::merge(std::move(Other)); +} + +void FunctionInfo::merge(FunctionInfo &&Other) { + assert(mergeable(Other)); + if (!IsMethod) + IsMethod = Other.IsMethod; + if (!Access) + Access = Other.Access; + if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "") + ReturnType = std::move(Other.ReturnType); + if (Parent.USR == EmptySID && Parent.Name == "") + Parent = std::move(Other.Parent); + if (Params.empty()) + Params = std::move(Other.Params); + SymbolInfo::merge(std::move(Other)); +} + +} // namespace doc +} // namespace clang diff --git a/clang-doc/Representation.h b/clang-doc/Representation.h new file mode 100644 index 000000000..f952954f9 --- /dev/null +++ b/clang-doc/Representation.h @@ -0,0 +1,251 @@ +///===-- Representation.h - ClangDoc Representation -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the internal representations of different declaration +// types for the clang-doc tool. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H + +#include "clang/AST/Type.h" +#include "clang/Basic/Specifiers.h" +#include "clang/Tooling/StandaloneExecution.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include +#include + +namespace clang { +namespace doc { + +// SHA1'd hash of a USR. +using SymbolID = std::array; + +struct Info; +enum class InfoType { + IT_default, + IT_namespace, + IT_record, + IT_function, + IT_enum +}; + +// A representation of a parsed comment. +struct CommentInfo { + CommentInfo() = default; + CommentInfo(CommentInfo &Other) = delete; + CommentInfo(CommentInfo &&Other) = default; + + SmallString<16> Kind; // Kind of comment (TextComment, InlineCommandComment, + // HTMLStartTagComment, HTMLEndTagComment, + // BlockCommandComment, ParamCommandComment, + // TParamCommandComment, VerbatimBlockComment, + // VerbatimBlockLineComment, VerbatimLineComment). + SmallString<64> Text; // Text of the comment. + SmallString<16> Name; // Name of the comment (for Verbatim and HTML). + SmallString<8> Direction; // Parameter direction (for (T)ParamCommand). + SmallString<16> ParamName; // Parameter name (for (T)ParamCommand). + SmallString<16> CloseName; // Closing tag name (for VerbatimBlock). + bool SelfClosing = false; // Indicates if tag is self-closing (for HTML). + bool Explicit = false; // Indicates if the direction of a param is explicit + // (for (T)ParamCommand). + llvm::SmallVector, 4> + AttrKeys; // List of attribute keys (for HTML). + llvm::SmallVector, 4> + AttrValues; // List of attribute values for each key (for HTML). + llvm::SmallVector, 4> + Args; // List of arguments to commands (for InlineCommand). + std::vector> + Children; // List of child comments for this CommentInfo. +}; + +struct Reference { + Reference() = default; + Reference(llvm::StringRef Name) : Name(Name) {} + Reference(SymbolID USR, StringRef Name, InfoType IT) + : USR(USR), Name(Name), RefType(IT) {} + + bool operator==(const Reference &Other) const { + return std::tie(USR, Name, RefType) == + std::tie(Other.USR, Other.Name, Other.RefType); + } + + SymbolID USR = SymbolID(); // Unique identifer for referenced decl + SmallString<16> Name; // Name of type (possibly unresolved). + InfoType RefType = InfoType::IT_default; // Indicates the type of this + // Reference (namespace, record, + // function, enum, default). +}; + +// A base struct for TypeInfos +struct TypeInfo { + TypeInfo() = default; + TypeInfo(SymbolID Type, StringRef Field, InfoType IT) + : Type(Type, Field, IT) {} + TypeInfo(llvm::StringRef RefName) : Type(RefName) {} + + bool operator==(const TypeInfo &Other) const { return Type == Other.Type; } + + Reference Type; // Referenced type in this info. +}; + +// Info for field types. +struct FieldTypeInfo : public TypeInfo { + FieldTypeInfo() = default; + FieldTypeInfo(SymbolID Type, StringRef Field, InfoType IT, + llvm::StringRef Name) + : TypeInfo(Type, Field, IT), Name(Name) {} + FieldTypeInfo(llvm::StringRef RefName, llvm::StringRef Name) + : TypeInfo(RefName), Name(Name) {} + + bool operator==(const FieldTypeInfo &Other) const { + return std::tie(Type, Name) == std::tie(Other.Type, Other.Name); + } + + SmallString<16> Name; // Name associated with this info. +}; + +// Info for member types. +struct MemberTypeInfo : public FieldTypeInfo { + MemberTypeInfo() = default; + MemberTypeInfo(SymbolID Type, StringRef Field, InfoType IT, + llvm::StringRef Name, AccessSpecifier Access) + : FieldTypeInfo(Type, Field, IT, Name), Access(Access) {} + MemberTypeInfo(llvm::StringRef RefName, llvm::StringRef Name, + AccessSpecifier Access) + : FieldTypeInfo(RefName, Name), Access(Access) {} + + bool operator==(const MemberTypeInfo &Other) const { + return std::tie(Type, Name, Access) == + std::tie(Other.Type, Other.Name, Other.Access); + } + + AccessSpecifier Access = AccessSpecifier::AS_none; // Access level associated + // with this info (public, + // protected, private, + // none). +}; + +struct Location { + Location() = default; + Location(int LineNumber, SmallString<16> Filename) + : LineNumber(LineNumber), Filename(std::move(Filename)) {} + + bool operator==(const Location &Other) const { + return std::tie(LineNumber, Filename) == + std::tie(Other.LineNumber, Other.Filename); + } + + int LineNumber; // Line number of this Location. + SmallString<32> Filename; // File for this Location. +}; + +/// A base struct for Infos. +struct Info { + Info() = default; + Info(InfoType IT) : IT(IT) {} + Info(const Info &Other) = delete; + Info(Info &&Other) = default; + + SymbolID USR = + SymbolID(); // Unique identifier for the decl described by this Info. + const InfoType IT = InfoType::IT_default; // InfoType of this particular Info. + SmallString<16> Name; // Unqualified name of the decl. + llvm::SmallVector + Namespace; // List of parent namespaces for this decl. + std::vector Description; // Comment description of this decl. + + void mergeBase(Info &&I); + bool mergeable(const Info &Other); +}; + +// Info for namespaces. +struct NamespaceInfo : public Info { + NamespaceInfo() : Info(InfoType::IT_namespace) {} + + void merge(NamespaceInfo &&I); +}; + +// Info for symbols. +struct SymbolInfo : public Info { + SymbolInfo(InfoType IT) : Info(IT) {} + + void merge(SymbolInfo &&I); + + llvm::Optional DefLoc; // Location where this decl is defined. + llvm::SmallVector Loc; // Locations where this decl is declared. +}; + +// TODO: Expand to allow for documenting templating and default args. +// Info for functions. +struct FunctionInfo : public SymbolInfo { + FunctionInfo() : SymbolInfo(InfoType::IT_function) {} + + void merge(FunctionInfo &&I); + + bool IsMethod = false; // Indicates whether this function is a class method. + Reference Parent; // Reference to the parent class decl for this method. + TypeInfo ReturnType; // Info about the return type of this function. + llvm::SmallVector Params; // List of parameters. + // Access level for this method (public, private, protected, none). + AccessSpecifier Access = AccessSpecifier::AS_none; +}; + +// TODO: Expand to allow for documenting templating, inheritance access, +// friend classes +// Info for types. +struct RecordInfo : public SymbolInfo { + RecordInfo() : SymbolInfo(InfoType::IT_record) {} + + void merge(RecordInfo &&I); + + TagTypeKind TagType = TagTypeKind::TTK_Struct; // Type of this record + // (struct, class, union, + // interface). + llvm::SmallVector + Members; // List of info about record members. + llvm::SmallVector Parents; // List of base/parent records + // (does not include virtual + // parents). + llvm::SmallVector + VirtualParents; // List of virtual base/parent records. +}; + +// TODO: Expand to allow for documenting templating. +// Info for types. +struct EnumInfo : public SymbolInfo { + EnumInfo() : SymbolInfo(InfoType::IT_enum) {} + + void merge(EnumInfo &&I); + + bool Scoped = + false; // Indicates whether this enum is scoped (e.g. enum class). + llvm::SmallVector, 4> Members; // List of enum members. +}; + +// TODO: Add functionality to include separate markdown pages. + +// A standalone function to call to merge a vector of infos into one. +// This assumes that all infos in the vector are of the same type, and will fail +// if they are different. +llvm::Expected> +mergeInfos(std::vector> &Values); + +struct ClangDocContext { + tooling::ExecutionContext *ECtx; + bool PublicOnly; +}; + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H diff --git a/clang-doc/Serialize.cpp b/clang-doc/Serialize.cpp new file mode 100644 index 000000000..c1e6d316f --- /dev/null +++ b/clang-doc/Serialize.cpp @@ -0,0 +1,368 @@ +//===-- Serializer.cpp - ClangDoc Serializer --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Serialize.h" +#include "BitcodeWriter.h" +#include "clang/AST/Comment.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/SHA1.h" + +using clang::comments::FullComment; + +namespace clang { +namespace doc { +namespace serialize { + +SymbolID hashUSR(llvm::StringRef USR) { + return llvm::SHA1::hash(arrayRefFromStringRef(USR)); +} + +class ClangDocCommentVisitor + : public ConstCommentVisitor { +public: + ClangDocCommentVisitor(CommentInfo &CI) : CurrentCI(CI) {} + + void parseComment(const comments::Comment *C); + + void visitTextComment(const TextComment *C); + void visitInlineCommandComment(const InlineCommandComment *C); + void visitHTMLStartTagComment(const HTMLStartTagComment *C); + void visitHTMLEndTagComment(const HTMLEndTagComment *C); + void visitBlockCommandComment(const BlockCommandComment *C); + void visitParamCommandComment(const ParamCommandComment *C); + void visitTParamCommandComment(const TParamCommandComment *C); + void visitVerbatimBlockComment(const VerbatimBlockComment *C); + void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); + void visitVerbatimLineComment(const VerbatimLineComment *C); + +private: + std::string getCommandName(unsigned CommandID) const; + bool isWhitespaceOnly(StringRef S) const; + + CommentInfo &CurrentCI; +}; + +void ClangDocCommentVisitor::parseComment(const comments::Comment *C) { + CurrentCI.Kind = C->getCommentKindName(); + ConstCommentVisitor::visit(C); + for (comments::Comment *Child : + llvm::make_range(C->child_begin(), C->child_end())) { + CurrentCI.Children.emplace_back(llvm::make_unique()); + ClangDocCommentVisitor Visitor(*CurrentCI.Children.back()); + Visitor.parseComment(Child); + } +} + +void ClangDocCommentVisitor::visitTextComment(const TextComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI.Text = C->getText(); +} + +void ClangDocCommentVisitor::visitInlineCommandComment( + const InlineCommandComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + for (unsigned I = 0, E = C->getNumArgs(); I != E; ++I) + CurrentCI.Args.push_back(C->getArgText(I)); +} + +void ClangDocCommentVisitor::visitHTMLStartTagComment( + const HTMLStartTagComment *C) { + CurrentCI.Name = C->getTagName(); + CurrentCI.SelfClosing = C->isSelfClosing(); + for (unsigned I = 0, E = C->getNumAttrs(); I < E; ++I) { + const HTMLStartTagComment::Attribute &Attr = C->getAttr(I); + CurrentCI.AttrKeys.push_back(Attr.Name); + CurrentCI.AttrValues.push_back(Attr.Value); + } +} + +void ClangDocCommentVisitor::visitHTMLEndTagComment( + const HTMLEndTagComment *C) { + CurrentCI.Name = C->getTagName(); + CurrentCI.SelfClosing = true; +} + +void ClangDocCommentVisitor::visitBlockCommandComment( + const BlockCommandComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + for (unsigned I = 0, E = C->getNumArgs(); I < E; ++I) + CurrentCI.Args.push_back(C->getArgText(I)); +} + +void ClangDocCommentVisitor::visitParamCommandComment( + const ParamCommandComment *C) { + CurrentCI.Direction = + ParamCommandComment::getDirectionAsString(C->getDirection()); + CurrentCI.Explicit = C->isDirectionExplicit(); + if (C->hasParamName()) + CurrentCI.ParamName = C->getParamNameAsWritten(); +} + +void ClangDocCommentVisitor::visitTParamCommandComment( + const TParamCommandComment *C) { + if (C->hasParamName()) + CurrentCI.ParamName = C->getParamNameAsWritten(); +} + +void ClangDocCommentVisitor::visitVerbatimBlockComment( + const VerbatimBlockComment *C) { + CurrentCI.Name = getCommandName(C->getCommandID()); + CurrentCI.CloseName = C->getCloseName(); +} + +void ClangDocCommentVisitor::visitVerbatimBlockLineComment( + const VerbatimBlockLineComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI.Text = C->getText(); +} + +void ClangDocCommentVisitor::visitVerbatimLineComment( + const VerbatimLineComment *C) { + if (!isWhitespaceOnly(C->getText())) + CurrentCI.Text = C->getText(); +} + +bool ClangDocCommentVisitor::isWhitespaceOnly(llvm::StringRef S) const { + return std::all_of(S.begin(), S.end(), isspace); +} + +std::string ClangDocCommentVisitor::getCommandName(unsigned CommandID) const { + const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); + if (Info) + return Info->Name; + // TODO: Add parsing for \file command. + return ""; +} + +// Serializing functions. + +template static std::string serialize(T &I) { + SmallString<2048> Buffer; + llvm::BitstreamWriter Stream(Buffer); + ClangDocBitcodeWriter Writer(Stream); + Writer.emitBlock(I); + return Buffer.str().str(); +} + +static void parseFullComment(const FullComment *C, CommentInfo &CI) { + ClangDocCommentVisitor Visitor(CI); + Visitor.parseComment(C); +} + +static SymbolID getUSRForDecl(const Decl *D) { + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(D, USR)) + return SymbolID(); + return hashUSR(USR); +} + +static RecordDecl *getDeclForType(const QualType &T) { + auto *Ty = T->getAs(); + if (!Ty) + return nullptr; + return Ty->getDecl()->getDefinition(); +} + +static bool isPublic(const clang::AccessSpecifier AS, + const clang::Linkage Link) { + if (AS == clang::AccessSpecifier::AS_private) + return false; + else if ((Link == clang::Linkage::ModuleLinkage) || + (Link == clang::Linkage::ExternalLinkage)) + return true; + return false; // otherwise, linkage is some form of internal linkage +} + +static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly) { + for (const FieldDecl *F : D->fields()) { + if (PublicOnly && !isPublic(F->getAccessUnsafe(), F->getLinkageInternal())) + continue; + if (const auto *T = getDeclForType(F->getTypeSourceInfo()->getType())) { + // Use getAccessUnsafe so that we just get the default AS_none if it's not + // valid, as opposed to an assert. + if (const auto *N = dyn_cast(T)) { + I.Members.emplace_back(getUSRForDecl(T), N->getNameAsString(), + InfoType::IT_enum, F->getNameAsString(), + N->getAccessUnsafe()); + continue; + } else if (const auto *N = dyn_cast(T)) { + I.Members.emplace_back(getUSRForDecl(T), N->getNameAsString(), + InfoType::IT_record, F->getNameAsString(), + N->getAccessUnsafe()); + continue; + } + } + I.Members.emplace_back(F->getTypeSourceInfo()->getType().getAsString(), + F->getNameAsString(), F->getAccessUnsafe()); + } +} + +static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { + for (const EnumConstantDecl *E : D->enumerators()) + I.Members.emplace_back(E->getNameAsString()); +} + +static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { + for (const ParmVarDecl *P : D->parameters()) { + if (const auto *T = getDeclForType(P->getOriginalType())) { + if (const auto *N = dyn_cast(T)) { + I.Params.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_enum, P->getNameAsString()); + continue; + } else if (const auto *N = dyn_cast(T)) { + I.Params.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_record, P->getNameAsString()); + continue; + } + } + I.Params.emplace_back(P->getOriginalType().getAsString(), + P->getNameAsString()); + } +} + +static void parseBases(RecordInfo &I, const CXXRecordDecl *D) { + for (const CXXBaseSpecifier &B : D->bases()) { + if (B.isVirtual()) + continue; + if (const auto *P = getDeclForType(B.getType())) + I.Parents.emplace_back(getUSRForDecl(P), P->getNameAsString(), + InfoType::IT_record); + else + I.Parents.emplace_back(B.getType().getAsString()); + } + for (const CXXBaseSpecifier &B : D->vbases()) { + if (const auto *P = getDeclForType(B.getType())) + I.VirtualParents.emplace_back(getUSRForDecl(P), P->getNameAsString(), + InfoType::IT_record); + else + I.VirtualParents.emplace_back(B.getType().getAsString()); + } +} + +template +static void +populateParentNamespaces(llvm::SmallVector &Namespaces, + const T *D) { + const auto *DC = dyn_cast(D); + while ((DC = DC->getParent())) { + if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_namespace); + else if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_record); + else if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_function); + else if (const auto *N = dyn_cast(DC)) + Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), + InfoType::IT_enum); + } +} + +template +static void populateInfo(Info &I, const T *D, const FullComment *C) { + I.USR = getUSRForDecl(D); + I.Name = D->getNameAsString(); + populateParentNamespaces(I.Namespace, D); + if (C) { + I.Description.emplace_back(); + parseFullComment(C, I.Description.back()); + } +} + +template +static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, + int LineNumber, StringRef Filename) { + populateInfo(I, D, C); + if (D->isThisDeclarationADefinition()) + I.DefLoc.emplace(LineNumber, Filename); + else + I.Loc.emplace_back(LineNumber, Filename); +} + +static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, + const FullComment *FC, int LineNumber, + StringRef Filename) { + populateSymbolInfo(I, D, FC, LineNumber, Filename); + if (const auto *T = getDeclForType(D->getReturnType())) { + if (dyn_cast(T)) + I.ReturnType = + TypeInfo(getUSRForDecl(T), T->getNameAsString(), InfoType::IT_enum); + else if (dyn_cast(T)) + I.ReturnType = + TypeInfo(getUSRForDecl(T), T->getNameAsString(), InfoType::IT_record); + } else { + I.ReturnType = TypeInfo(D->getReturnType().getAsString()); + } + parseParameters(I, D); +} + +std::string emitInfo(const NamespaceDecl *D, const FullComment *FC, + int LineNumber, llvm::StringRef File, bool PublicOnly) { + if (PublicOnly && ((D->isAnonymousNamespace()) || + !isPublic(D->getAccess(), D->getLinkageInternal()))) + return ""; + NamespaceInfo I; + populateInfo(I, D, FC); + return serialize(I); +} + +std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { + if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) + return ""; + RecordInfo I; + populateSymbolInfo(I, D, FC, LineNumber, File); + I.TagType = D->getTagKind(); + parseFields(I, D, PublicOnly); + if (const auto *C = dyn_cast(D)) + parseBases(I, C); + return serialize(I); +} + +std::string emitInfo(const FunctionDecl *D, const FullComment *FC, + int LineNumber, llvm::StringRef File, bool PublicOnly) { + if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) + return ""; + FunctionInfo I; + populateFunctionInfo(I, D, FC, LineNumber, File); + I.Access = clang::AccessSpecifier::AS_none; + return serialize(I); +} + +std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC, + int LineNumber, llvm::StringRef File, bool PublicOnly) { + if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) + return ""; + FunctionInfo I; + populateFunctionInfo(I, D, FC, LineNumber, File); + I.IsMethod = true; + I.Parent = Reference{getUSRForDecl(D->getParent()), + D->getParent()->getNameAsString(), InfoType::IT_record}; + I.Access = D->getAccess(); + return serialize(I); +} + +std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, + llvm::StringRef File, bool PublicOnly) { + if (PublicOnly && !isPublic(D->getAccess(), D->getLinkageInternal())) + return ""; + EnumInfo I; + populateSymbolInfo(I, D, FC, LineNumber, File); + I.Scoped = D->isScoped(); + parseEnumerators(I, D); + return serialize(I); +} + +} // namespace serialize +} // namespace doc +} // namespace clang diff --git a/clang-doc/Serialize.h b/clang-doc/Serialize.h new file mode 100644 index 000000000..5181cf61b --- /dev/null +++ b/clang-doc/Serialize.h @@ -0,0 +1,53 @@ +//===-- Serializer.h - ClangDoc Serializer ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the serializing functions fro the clang-doc tool. Given +// a particular declaration, it collects the appropriate information and returns +// a serialized bitcode string for the declaration. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H + +#include "Representation.h" +#include "clang/AST/AST.h" +#include "clang/AST/CommentVisitor.h" +#include +#include + +using namespace clang::comments; + +namespace clang { +namespace doc { +namespace serialize { + +std::string emitInfo(const NamespaceDecl *D, const FullComment *FC, + int LineNumber, StringRef File, bool PublicOnly); +std::string emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); +std::string emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool PublicOnly); +std::string emitInfo(const FunctionDecl *D, const FullComment *FC, + int LineNumber, StringRef File, bool PublicOnly); +std::string emitInfo(const CXXMethodDecl *D, const FullComment *FC, + int LineNumber, StringRef File, bool PublicOnly); + +// Function to hash a given USR value for storage. +// As USRs (Unified Symbol Resolution) could be large, especially for functions +// with long type arguments, we use 160-bits SHA1(USR) values to +// guarantee the uniqueness of symbols while using a relatively small amount of +// memory (vs storing USRs directly). +SymbolID hashUSR(llvm::StringRef USR); + +} // namespace serialize +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_SERIALIZE_H diff --git a/clang-doc/YAMLGenerator.cpp b/clang-doc/YAMLGenerator.cpp new file mode 100644 index 000000000..f29b4787d --- /dev/null +++ b/clang-doc/YAMLGenerator.cpp @@ -0,0 +1,268 @@ +//===-- ClangDocYAML.cpp - ClangDoc YAML -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Implementation of the YAML generator, converting decl info into YAML output. +//===----------------------------------------------------------------------===// + +#include "Generators.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang::doc; + +LLVM_YAML_IS_SEQUENCE_VECTOR(FieldTypeInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(MemberTypeInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(Reference) +LLVM_YAML_IS_SEQUENCE_VECTOR(Location) +LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr) +LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>) + +namespace llvm { +namespace yaml { + +// Enumerations to YAML output. + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, clang::AccessSpecifier &Value) { + IO.enumCase(Value, "Public", clang::AccessSpecifier::AS_public); + IO.enumCase(Value, "Protected", clang::AccessSpecifier::AS_protected); + IO.enumCase(Value, "Private", clang::AccessSpecifier::AS_private); + IO.enumCase(Value, "None", clang::AccessSpecifier::AS_none); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, clang::TagTypeKind &Value) { + IO.enumCase(Value, "Struct", clang::TagTypeKind::TTK_Struct); + IO.enumCase(Value, "Interface", clang::TagTypeKind::TTK_Interface); + IO.enumCase(Value, "Union", clang::TagTypeKind::TTK_Union); + IO.enumCase(Value, "Class", clang::TagTypeKind::TTK_Class); + IO.enumCase(Value, "Enum", clang::TagTypeKind::TTK_Enum); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, InfoType &Value) { + IO.enumCase(Value, "Namespace", InfoType::IT_namespace); + IO.enumCase(Value, "Record", InfoType::IT_record); + IO.enumCase(Value, "Function", InfoType::IT_function); + IO.enumCase(Value, "Enum", InfoType::IT_enum); + IO.enumCase(Value, "Default", InfoType::IT_default); + } +}; + +// Scalars to YAML output. +template struct ScalarTraits> { + + static void output(const SmallString &S, void *, llvm::raw_ostream &OS) { + for (const auto &C : S) + OS << C; + } + + static StringRef input(StringRef Scalar, void *, SmallString &Value) { + Value.assign(Scalar.begin(), Scalar.end()); + return StringRef(); + } + + static QuotingType mustQuote(StringRef) { return QuotingType::Single; } +}; + +template <> struct ScalarTraits> { + + static void output(const std::array &S, void *, + llvm::raw_ostream &OS) { + OS << toHex(toStringRef(S)); + } + + static StringRef input(StringRef Scalar, void *, + std::array &Value) { + if (Scalar.size() != 40) + return "Error: Incorrect scalar size for USR."; + Value = StringToSymbol(Scalar); + return StringRef(); + } + + static SymbolID StringToSymbol(llvm::StringRef Value) { + SymbolID USR; + std::string HexString = fromHex(Value); + std::copy(HexString.begin(), HexString.end(), USR.begin()); + return SymbolID(USR); + } + + static QuotingType mustQuote(StringRef) { return QuotingType::Single; } +}; + +// Helper functions to map infos to YAML. + +static void TypeInfoMapping(IO &IO, TypeInfo &I) { + IO.mapOptional("Type", I.Type, Reference()); +} + +static void FieldTypeInfoMapping(IO &IO, FieldTypeInfo &I) { + TypeInfoMapping(IO, I); + IO.mapOptional("Name", I.Name, SmallString<16>()); +} + +static void InfoMapping(IO &IO, Info &I) { + IO.mapRequired("USR", I.USR); + IO.mapOptional("Name", I.Name, SmallString<16>()); + IO.mapOptional("Namespace", I.Namespace, llvm::SmallVector()); + IO.mapOptional("Description", I.Description); +} + +static void SymbolInfoMapping(IO &IO, SymbolInfo &I) { + InfoMapping(IO, I); + IO.mapOptional("DefLocation", I.DefLoc, Optional()); + IO.mapOptional("Location", I.Loc, llvm::SmallVector()); +} + +static void CommentInfoMapping(IO &IO, CommentInfo &I) { + IO.mapOptional("Kind", I.Kind, SmallString<16>()); + IO.mapOptional("Text", I.Text, SmallString<64>()); + IO.mapOptional("Name", I.Name, SmallString<16>()); + IO.mapOptional("Direction", I.Direction, SmallString<8>()); + IO.mapOptional("ParamName", I.ParamName, SmallString<16>()); + IO.mapOptional("CloseName", I.CloseName, SmallString<16>()); + IO.mapOptional("SelfClosing", I.SelfClosing, false); + IO.mapOptional("Explicit", I.Explicit, false); + IO.mapOptional("Args", I.Args, llvm::SmallVector, 4>()); + IO.mapOptional("AttrKeys", I.AttrKeys, + llvm::SmallVector, 4>()); + IO.mapOptional("AttrValues", I.AttrValues, + llvm::SmallVector, 4>()); + IO.mapOptional("Children", I.Children); +} + +// Template specialization to YAML traits for Infos. + +template <> struct MappingTraits { + static void mapping(IO &IO, Location &Loc) { + IO.mapOptional("LineNumber", Loc.LineNumber, 0); + IO.mapOptional("Filename", Loc.Filename, SmallString<32>()); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, Reference &Ref) { + IO.mapOptional("Type", Ref.RefType, InfoType::IT_default); + IO.mapOptional("Name", Ref.Name, SmallString<16>()); + IO.mapOptional("USR", Ref.USR, SymbolID()); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TypeInfo &I) { TypeInfoMapping(IO, I); } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, FieldTypeInfo &I) { + TypeInfoMapping(IO, I); + IO.mapOptional("Name", I.Name, SmallString<16>()); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, MemberTypeInfo &I) { + FieldTypeInfoMapping(IO, I); + IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, NamespaceInfo &I) { InfoMapping(IO, I); } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, RecordInfo &I) { + SymbolInfoMapping(IO, I); + IO.mapOptional("TagType", I.TagType, clang::TagTypeKind::TTK_Struct); + IO.mapOptional("Members", I.Members); + IO.mapOptional("Parents", I.Parents, llvm::SmallVector()); + IO.mapOptional("VirtualParents", I.VirtualParents, + llvm::SmallVector()); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, EnumInfo &I) { + SymbolInfoMapping(IO, I); + IO.mapOptional("Scoped", I.Scoped, false); + IO.mapOptional("Members", I.Members); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, FunctionInfo &I) { + SymbolInfoMapping(IO, I); + IO.mapOptional("IsMethod", I.IsMethod, false); + IO.mapOptional("Parent", I.Parent, Reference()); + IO.mapOptional("Params", I.Params); + IO.mapOptional("ReturnType", I.ReturnType); + IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, CommentInfo &I) { CommentInfoMapping(IO, I); } +}; + +template <> struct MappingTraits> { + static void mapping(IO &IO, std::unique_ptr &I) { + if (I) + CommentInfoMapping(IO, *I); + } +}; + +} // end namespace yaml +} // end namespace llvm + +namespace clang { +namespace doc { + +/// Generator for YAML documentation. +class YAMLGenerator : public Generator { +public: + static const char *Format; + + bool generateDocForInfo(Info *I, llvm::raw_ostream &OS) override; +}; + +const char *YAMLGenerator::Format = "yaml"; + +bool YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) { + llvm::yaml::Output InfoYAML(OS); + switch (I->IT) { + case InfoType::IT_namespace: + InfoYAML << *static_cast(I); + break; + case InfoType::IT_record: + InfoYAML << *static_cast(I); + break; + case InfoType::IT_enum: + InfoYAML << *static_cast(I); + break; + case InfoType::IT_function: + InfoYAML << *static_cast(I); + break; + case InfoType::IT_default: + llvm::errs() << "Unexpected info type in index.\n"; + return true; + } + return false; +} + +static GeneratorRegistry::Add YAML(YAMLGenerator::Format, + "Generator for YAML output."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the generator. +volatile int YAMLGeneratorAnchorSource = 0; + +} // namespace doc +} // namespace clang diff --git a/clang-doc/gen_tests.py b/clang-doc/gen_tests.py new file mode 100644 index 000000000..5004892e5 --- /dev/null +++ b/clang-doc/gen_tests.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# +#===- gen_tests.py - clang-doc test generator ----------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +""" +clang-doc test generator +========================== + +Generates tests for clang-doc given a certain set of flags, a prefix for the +test file, and a given clang-doc binary. Please check emitted tests for +accuracy before using. + +To generate all current tests: +- Generate mapper tests: + gen_tests.py -flag='--dump-mapper' -flag='--doxygen' -prefix mapper + +- Generate reducer tests: + gen_tests.py -flag='--dump-intermediate' -flag='--doxygen' -prefix bc + +- Generate yaml tests: + gen_tests.py -flag='--format=yaml' -flag='--doxygen' -prefix yaml + +This script was written on/for Linux, and has not been tested on any other +platform and so it may not work. + +""" + +import argparse +import glob +import os +import shutil +import subprocess + +RUN_CLANG_DOC = """ +// RUN: clang-doc {0} -p %t %t/test.cpp -output=%t/docs +""" +RUN = """ +// RUN: {0} %t/{1} | FileCheck %s --check-prefix CHECK-{2} +""" + +CHECK = '// CHECK-{0}: ' + +CHECK_NEXT = '// CHECK-{0}-NEXT: ' + + +def clear_test_prefix_files(prefix, tests_path): + if os.path.isdir(tests_path): + for root, dirs, files in os.walk(tests_path): + for filename in files: + if filename.startswith(prefix): + os.remove(os.path.join(root, filename)) + + +def copy_to_test_file(test_case_path, test_cases_path): + # Copy file to 'test.cpp' to preserve file-dependent USRs + test_file = os.path.join(test_cases_path, 'test.cpp') + shutil.copyfile(test_case_path, test_file) + return test_file + + +def run_clang_doc(args, out_dir, test_file): + # Run clang-doc. + current_cmd = [args.clangdoc] + current_cmd.extend(args.flags) + current_cmd.append('--output=' + out_dir) + current_cmd.append(test_file) + print('Running ' + ' '.join(current_cmd)) + return_code = subprocess.call(current_cmd) + if return_code: + return 1 + return 0 + + +def get_test_case_code(test_case_path, flags): + # Get the test case code + code = '' + with open(test_case_path, 'r') as code_file: + code = code_file.read() + + code += RUN_CLANG_DOC.format(flags) + return code + + +def get_output(root, out_file, case_out_path, flags, checkname, bcanalyzer): + output = '' + run_cmd = '' + if '--dump-mapper' in flags or '--dump-intermediate' in flags: + # Run llvm-bcanalyzer + output = subprocess.check_output( + [bcanalyzer, '--dump', + os.path.join(root, out_file)]) + output = output[:output.find('Summary of ')].rstrip() + run_cmd = RUN.format('llvm-bcanalyzer --dump', + os.path.join('docs', 'bc', out_file), checkname) + else: + # Run cat + output = subprocess.check_output(['cat', os.path.join(root, out_file)]) + run_cmd = RUN.format( + 'cat', + os.path.join('docs', os.path.relpath(root, case_out_path), + out_file), checkname) + + # Format output. + output = output.replace('blob data = \'test\'', 'blob data = \'{{.*}}\'') + output = CHECK.format(checkname) + output.rstrip() + output = run_cmd + output.replace('\n', + '\n' + CHECK_NEXT.format(checkname)) + + return output + '\n' + + +def main(): + parser = argparse.ArgumentParser(description='Generate clang-doc tests.') + parser.add_argument( + '-flag', + action='append', + default=[], + dest='flags', + help='Flags to pass to clang-doc.') + parser.add_argument( + '-prefix', + type=str, + default='', + dest='prefix', + help='Prefix for this test group.') + parser.add_argument( + '-clang-doc-binary', + dest='clangdoc', + metavar="PATH", + default='clang-doc', + help='path to clang-doc binary') + parser.add_argument( + '-llvm-bcanalyzer-binary', + dest='bcanalyzer', + metavar="PATH", + default='llvm-bcanalyzer', + help='path to llvm-bcanalyzer binary') + args = parser.parse_args() + + flags = ' '.join(args.flags) + + clang_doc_path = os.path.dirname(__file__) + tests_path = os.path.join(clang_doc_path, '..', 'test', 'clang-doc') + test_cases_path = os.path.join(tests_path, 'test_cases') + + clear_test_prefix_files(args.prefix, tests_path) + + for test_case_path in glob.glob(os.path.join(test_cases_path, '*')): + if test_case_path.endswith( + 'compile_flags.txt') or test_case_path.endswith( + 'compile_commands.json'): + continue + + # Name of this test case + case_name = os.path.basename(test_case_path).split('.')[0] + + test_file = copy_to_test_file(test_case_path, test_cases_path) + out_dir = os.path.join(test_cases_path, case_name) + + if run_clang_doc(args, out_dir, test_file): + return 1 + + # Retrieve output and format as FileCheck tests + all_output = '' + num_outputs = 0 + for root, dirs, files in os.walk(out_dir): + for out_file in files: + # Make the file check the first 3 letters (there's a very small chance + # that this will collide, but the fix is to simply change the decl name) + usr = os.path.basename(out_file).split('.') + # If the usr is less than 2, this isn't one of the test files. + if len(usr) < 2: + continue + all_output += get_output(root, out_file, out_dir, args.flags, + num_outputs, args.bcanalyzer) + num_outputs += 1 + + # Add test case code to test + all_output = get_test_case_code(test_case_path, + flags) + '\n' + all_output + + # Write to test case file in /test. + test_out_path = os.path.join( + tests_path, args.prefix + '-' + os.path.basename(test_case_path)) + with open(test_out_path, 'w+') as o: + o.write(all_output) + + # Clean up + shutil.rmtree(out_dir) + os.remove(test_file) + + +if __name__ == '__main__': + main() diff --git a/clang-doc/tool/CMakeLists.txt b/clang-doc/tool/CMakeLists.txt new file mode 100644 index 000000000..d7f28cf68 --- /dev/null +++ b/clang-doc/tool/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-doc + ClangDocMain.cpp + ) + +target_link_libraries(clang-doc + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangDoc + clangTooling + clangToolingCore + ) + \ No newline at end of file diff --git a/clang-doc/tool/ClangDocMain.cpp b/clang-doc/tool/ClangDocMain.cpp new file mode 100644 index 000000000..9e89e8568 --- /dev/null +++ b/clang-doc/tool/ClangDocMain.cpp @@ -0,0 +1,253 @@ +//===-- ClangDocMain.cpp - ClangDoc -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool for generating C and C++ documenation from source code +// and comments. Generally, it runs a LibTooling FrontendAction on source files, +// mapping each declaration in those files to its USR and serializing relevant +// information into LLVM bitcode. It then runs a pass over the collected +// declaration information, reducing by USR. There is an option to dump this +// intermediate result to bitcode. Finally, it hands the reduced information +// off to a generator, which does the final parsing from the intermediate +// representation to the desired output format. +// +//===----------------------------------------------------------------------===// + +#include "BitcodeReader.h" +#include "BitcodeWriter.h" +#include "ClangDoc.h" +#include "Generators.h" +#include "Representation.h" +#include "clang/AST/AST.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/StandaloneExecution.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/APFloat.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang::ast_matchers; +using namespace clang::tooling; +using namespace clang; + +static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static llvm::cl::OptionCategory ClangDocCategory("clang-doc options"); + +static llvm::cl::opt + OutDirectory("output", + llvm::cl::desc("Directory for outputting generated files."), + llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory)); + +static llvm::cl::opt + DumpMapperResult("dump-mapper", + llvm::cl::desc("Dump mapper results to bitcode file."), + llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); + +static llvm::cl::opt DumpIntermediateResult( + "dump-intermediate", + llvm::cl::desc("Dump intermediate results to bitcode file."), + llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); + +static llvm::cl::opt + PublicOnly("public", llvm::cl::desc("Document only public declarations."), + llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); + +enum OutputFormatTy { + yaml, +}; + +static llvm::cl::opt FormatEnum( + "format", llvm::cl::desc("Format for outputted docs."), + llvm::cl::values(clEnumVal(yaml, "Documentation in YAML format.")), + llvm::cl::init(yaml), llvm::cl::cat(ClangDocCategory)); + +static llvm::cl::opt DoxygenOnly( + "doxygen", + llvm::cl::desc("Use only doxygen-style comments to generate docs."), + llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); + +bool CreateDirectory(const Twine &DirName, bool ClearDirectory = false) { + std::error_code OK; + llvm::SmallString<128> DocsRootPath; + if (ClearDirectory) { + std::error_code RemoveStatus = llvm::sys::fs::remove_directories(DirName); + if (RemoveStatus != OK) { + llvm::errs() << "Unable to remove existing documentation directory for " + << DirName << ".\n"; + return true; + } + } + std::error_code DirectoryStatus = llvm::sys::fs::create_directories(DirName); + if (DirectoryStatus != OK) { + llvm::errs() << "Unable to create documentation directories.\n"; + return true; + } + return false; +} + +bool DumpResultToFile(const Twine &DirName, const Twine &FileName, + StringRef Buffer, bool ClearDirectory = false) { + std::error_code OK; + llvm::SmallString<128> IRRootPath; + llvm::sys::path::native(OutDirectory, IRRootPath); + llvm::sys::path::append(IRRootPath, DirName); + if (CreateDirectory(IRRootPath, ClearDirectory)) + return true; + llvm::sys::path::append(IRRootPath, FileName); + std::error_code OutErrorInfo; + llvm::raw_fd_ostream OS(IRRootPath, OutErrorInfo, llvm::sys::fs::F_None); + if (OutErrorInfo != OK) { + llvm::errs() << "Error opening documentation file.\n"; + return true; + } + OS << Buffer; + OS.close(); + return false; +} + +llvm::Expected> +getPath(StringRef Root, StringRef Ext, StringRef Name, + llvm::SmallVectorImpl &Namespaces) { + std::error_code OK; + llvm::SmallString<128> Path; + llvm::sys::path::native(Root, Path); + for (auto R = Namespaces.rbegin(), E = Namespaces.rend(); R != E; ++R) + llvm::sys::path::append(Path, R->Name); + + if (CreateDirectory(Path)) + return llvm::make_error("Unable to create directory.\n", + llvm::inconvertibleErrorCode()); + + llvm::sys::path::append(Path, Name + Ext); + return Path; +} + +std::string getFormatString(OutputFormatTy Ty) { + switch (Ty) { + case yaml: + return "yaml"; + } + llvm_unreachable("Unknown OutputFormatTy"); +} + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + std::error_code OK; + + // Fail early if an invalid format was provided. + std::string Format = getFormatString(FormatEnum); + auto G = doc::findGeneratorByName(Format); + if (!G) { + llvm::errs() << toString(G.takeError()) << "\n"; + return 1; + } + + auto Exec = clang::tooling::createExecutorFromCommandLineArgs( + argc, argv, ClangDocCategory); + + if (!Exec) { + llvm::errs() << toString(Exec.takeError()) << "\n"; + return 1; + } + + ArgumentsAdjuster ArgAdjuster; + if (!DoxygenOnly) + ArgAdjuster = combineAdjusters( + getInsertArgumentAdjuster("-fparse-all-comments", + tooling::ArgumentInsertPosition::END), + ArgAdjuster); + + // Mapping phase + llvm::outs() << "Mapping decls...\n"; + clang::doc::ClangDocContext CDCtx = {Exec->get()->getExecutionContext(), + PublicOnly}; + auto Err = + Exec->get()->execute(doc::newMapperActionFactory(CDCtx), ArgAdjuster); + if (Err) { + llvm::errs() << toString(std::move(Err)) << "\n"; + return 1; + } + + if (DumpMapperResult) { + bool Err = false; + Exec->get()->getToolResults()->forEachResult( + [&](StringRef Key, StringRef Value) { + Err = DumpResultToFile("bc", Key + ".bc", Value); + }); + if (Err) + llvm::errs() << "Error dumping map results.\n"; + return Err; + } + + // Collect values into output by key. + llvm::outs() << "Collecting infos...\n"; + llvm::StringMap>> MapOutput; + + // In ToolResults, the Key is the hashed USR and the value is the + // bitcode-encoded representation of the Info object. + Exec->get()->getToolResults()->forEachResult([&](StringRef Key, + StringRef Value) { + llvm::BitstreamCursor Stream(Value); + doc::ClangDocBitcodeReader Reader(Stream); + auto Infos = Reader.readBitcode(); + for (auto &I : Infos) { + auto R = + MapOutput.try_emplace(Key, std::vector>()); + R.first->second.emplace_back(std::move(I)); + } + }); + + // Reducing and generation phases + llvm::outs() << "Reducing " << MapOutput.size() << " infos...\n"; + llvm::StringMap> ReduceOutput; + for (auto &Group : MapOutput) { + auto Reduced = doc::mergeInfos(Group.getValue()); + if (!Reduced) + llvm::errs() << llvm::toString(Reduced.takeError()); + + if (DumpIntermediateResult) { + SmallString<4096> Buffer; + llvm::BitstreamWriter Stream(Buffer); + doc::ClangDocBitcodeWriter Writer(Stream); + Writer.dispatchInfoForWrite(Reduced.get().get()); + if (DumpResultToFile("bc", Group.getKey() + ".bc", Buffer)) + llvm::errs() << "Error dumping to bitcode.\n"; + continue; + } + + // Create the relevant ostream and emit the documentation for this decl. + doc::Info *I = Reduced.get().get(); + auto InfoPath = getPath(OutDirectory, "." + Format, I->Name, I->Namespace); + if (!InfoPath) { + llvm::errs() << toString(InfoPath.takeError()) << "\n"; + continue; + } + std::error_code FileErr; + llvm::raw_fd_ostream InfoOS(InfoPath.get(), FileErr, llvm::sys::fs::F_None); + if (FileErr != OK) { + llvm::errs() << "Error opening index file: " << FileErr.message() << "\n"; + continue; + } + + if (G->get()->generateDocForInfo(I, InfoOS)) + llvm::errs() << "Unable to generate docs for info.\n"; + } + + 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..e126852e3 --- /dev/null +++ b/clang-move/ClangMove.cpp @@ -0,0 +1,954 @@ +//===-- 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); +} + +std::string CleanPath(StringRef PathRef) { + llvm::SmallString<128> Path(PathRef); + llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true); + // FIXME: figure out why this is necessary. + llvm::sys::path::native(Path); + return Path.str(); +} + +// 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'; + return CleanPath(std::move(AbsolutePath)); +} + +// 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); + // FIXME: getCanonicalName might fail to get real path on VFS. + if (llvm::sys::path::is_absolute(DirName)) { + SmallString<128> AbsoluteFilename; + llvm::sys::path::append(AbsoluteFilename, DirName, + llvm::sys::path::filename(AbsolutePath.str())); + return CleanPath(AbsoluteFilename); + } + } + return CleanPath(AbsolutePath); +} + +// 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*/, + SrcMgr::CharacteristicKind /*FileType*/) 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(); + // If the expansion range is a character range, this is the location of + // the first character past the end. Otherwise it's the location of the + // first character in the final token in the range. + auto EndExpansionLoc = SM.getExpansionRange(D->getLocEnd()).getEnd(); + 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. + notInMacro(), + 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); + LLVM_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 AbsoluteIncludeHeader = + MakeAbsolutePath(SM, llvm::StringRef(HeaderWithSearchPath.data(), + HeaderWithSearchPath.size())); + std::string IncludeLine = + IsAngled ? ("#include <" + IncludeHeader + ">\n").str() + : ("#include \"" + IncludeHeader + "\"\n").str(); + + std::string AbsoluteOldHeader = makeAbsolutePath(Context->Spec.OldHeader); + std::string AbsoluteCurrentFile = MakeAbsolutePath(SM, FileName); + if (AbsoluteOldHeader == AbsoluteCurrentFile) { + // Find old.h includes "old.h". + if (AbsoluteOldHeader == AbsoluteIncludeHeader) { + OldHeaderIncludeRangeInHeader = IncludeFilenameRange; + return; + } + HeaderIncludes.push_back(IncludeLine); + } else if (makeAbsolutePath(Context->Spec.OldCC) == AbsoluteCurrentFile) { + // Find old.cc includes "old.h". + if (AbsoluteOldHeader == AbsoluteIncludeHeader) { + OldHeaderIncludeRangeInCC = IncludeFilenameRange; + return; + } + 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) { + LLVM_DEBUG(llvm::dbgs() << "Check helper is used: " + << D->getNameAsString() << " (" << D << ")\n"); + if (!UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl( + D->getCanonicalDecl()))) { + LLVM_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; + + LLVM_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)); + auto ReplaceOldInclude = [&](clang::CharSourceRange OldHeaderIncludeRange) { + AllCode = AllCode.merge(clang::tooling::Replacements( + clang::tooling::Replacement(SM, OldHeaderIncludeRange, + '"' + Context->Spec.NewHeader + '"'))); + }; + // Fix the case where old.h/old.cc includes "old.h", we replace the + // `#include "old.h"` with `#include "new.h"`. + if (Context->Spec.NewCC == NewFile && OldHeaderIncludeRangeInCC.isValid()) + ReplaceOldInclude(OldHeaderIncludeRangeInCC); + else if (Context->Spec.NewHeader == NewFile && + OldHeaderIncludeRangeInHeader.isValid()) + ReplaceOldInclude(OldHeaderIncludeRangeInHeader); + 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 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; + } + LLVM_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..945c6db0a --- /dev/null +++ b/clang-move/ClangMove.h @@ -0,0 +1,233 @@ +//===-- 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 OldHeaderIncludeRangeInCC; + /// The source range for the written file name in #include (i.e. "old.h" for + /// #include "old.h") in old.h, including the enclosing quotes or angle + /// brackets. + clang::CharSourceRange OldHeaderIncludeRangeInHeader; + /// 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..28200e0b1 --- /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); + LLVM_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); + LLVM_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..9f678bb21 --- /dev/null +++ b/clang-move/tool/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-move + ClangMoveMain.cpp + ) + +target_link_libraries(clang-move + PRIVATE + 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..e5fee2635 --- /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::CD_CreateAlways, 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..bb3d1b3a3 --- /dev/null +++ b/clang-query/QueryParser.cpp @@ -0,0 +1,272 @@ +//===---- 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 { + StringRef Word; + StringSwitch Switch; + + QueryParser *P; + // Set to the completion point offset in Word, or StringRef::npos if + // completion point not in Word. + size_t WordCompletionPos; + + // 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. + LexOrCompleteWord(QueryParser *P, StringRef &OutWord) + : Word(P->lexWord()), Switch(Word), P(P), + WordCompletionPos(StringRef::npos) { + OutWord = Word; + if (P->CompletionPos && P->CompletionPos <= Word.data() + Word.size()) { + if (P->CompletionPos < Word.data()) + WordCompletionPos = 0; + else + WordCompletionPos = P->CompletionPos - Word.data(); + } + } + + LexOrCompleteWord &Case(llvm::StringLiteral CaseStr, const T &Value, + bool IsCompletion = true) { + + if (WordCompletionPos == StringRef::npos) + Switch.Case(CaseStr, Value); + else if (CaseStr.size() != 0 && 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(T Value) { return Switch.Default(Value); } +}; + +QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { + StringRef ValStr; + unsigned Value = LexOrCompleteWord(this, 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(this, 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(this, 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(this, + 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..67f907d1c --- /dev/null +++ b/clang-query/QueryParser.h @@ -0,0 +1,70 @@ +//===--- 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; + + 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..a95093186 --- /dev/null +++ b/clang-query/tool/CMakeLists.txt @@ -0,0 +1,15 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-query ClangQuery.cpp) +target_link_libraries(clang-query + PRIVATE + 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..7cb8abe53 --- /dev/null +++ b/clang-reorder-fields/ReorderFieldsAction.cpp @@ -0,0 +1,311 @@ +//===-- 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 "llvm/ADT/SetVector.h" +#include +#include + +namespace clang { +namespace reorder_fields { +using namespace clang::ast_matchers; +using llvm::SmallSetVector; + +/// \brief Finds the definition of a record by name. +/// +/// \returns nullptr if the name is ambiguous or not found. +static const RecordDecl *findDefinition(StringRef RecordName, + ASTContext &Context) { + auto Results = + match(recordDecl(hasName(RecordName), isDefinition()).bind("recordDecl"), + 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("recordDecl", 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 RecordDecl *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 Find all member fields used in the given init-list initializer expr +/// that belong to the same record +/// +/// \returns a set of field declarations, empty if none were present +static SmallSetVector +findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer, + ASTContext &Context) { + SmallSetVector Results; + // Note that this does not pick up member fields of base classes since + // for those accesses Sema::PerformObjectMemberConversion always inserts an + // UncheckedDerivedToBase ImplicitCastExpr between the this expr and the + // object expression + auto FoundExprs = + match(findAll(memberExpr(hasObjectExpression(cxxThisExpr())).bind("ME")), + *Initializer->getInit(), Context); + for (BoundNodes &BN : FoundExprs) + if (auto *MemExpr = BN.getNodeAs("ME")) + if (auto *FD = dyn_cast(MemExpr->getMemberDecl())) + Results.insert(FD); + return Results; +} + +/// \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 RecordDecl *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, + 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->isMemberInitializer() || !Initializer->isWritten()) + continue; + + // Warn if this reordering violates initialization expr dependencies. + const FieldDecl *ThisM = Initializer->getMember(); + const auto UsedMembers = findMembersUsedInInitExpr(Initializer, Context); + for (const FieldDecl *UM : UsedMembers) { + if (NewFieldsPositions[UM->getFieldIndex()] > + NewFieldsPositions[ThisM->getFieldIndex()]) { + DiagnosticsEngine &DiagEngine = Context.getDiagnostics(); + auto Description = ("reordering field " + UM->getName() + " after " + + ThisM->getName() + " makes " + UM->getName() + + " uninitialized when used in init expression") + .str(); + unsigned ID = DiagEngine.getDiagnosticIDs()->getCustomDiagID( + DiagnosticIDs::Warning, Description); + DiagEngine.Report(Initializer->getSourceLocation(), ID); + } + } + + 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 RecordDecl *RD = findDefinition(RecordName, Context); + if (!RD) + return; + SmallVector NewFieldsOrder = + getNewFieldsOrder(RD, DesiredFieldsOrder); + if (NewFieldsOrder.empty()) + return; + if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements)) + return; + + // CXXRD will be nullptr if C code (not C++) is being processed. + const CXXRecordDecl *CXXRD = dyn_cast(RD); + if (CXXRD) + for (const auto *C : CXXRD->ctors()) + if (const auto *D = dyn_cast(C->getDefinition())) + reorderFieldsInConstructor(cast(D), + NewFieldsOrder, Context, Replacements); + + // We only need to reorder init list expressions for + // plain C structs or C++ 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 (!CXXRD || CXXRD->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..2de2cecc0 --- /dev/null +++ b/clang-reorder-fields/tool/CMakeLists.txt @@ -0,0 +1,11 @@ +add_clang_tool(clang-reorder-fields ClangReorderFields.cpp) + +target_link_libraries(clang-reorder-fields + PRIVATE + clangBasic + clangFrontend + clangReorderFields + clangRewrite + clangTooling + clangToolingCore + ) 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..7b06aba73 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml @@ -0,0 +1,317 @@ +--- +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 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..547f6a489 --- /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-2018 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..a87246942 --- /dev/null +++ b/clang-tidy/CMakeLists.txt @@ -0,0 +1,50 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangTidy + ClangTidy.cpp + ClangTidyModule.cpp + ClangTidyDiagnosticConsumer.cpp + ClangTidyOptions.cpp + ClangTidyProfiling.cpp + + DEPENDS + ClangSACheckers + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangRewrite + clangSema + clangStaticAnalyzerCore + clangStaticAnalyzerFrontend + clangTooling + clangToolingCore + ) + +add_subdirectory(android) +add_subdirectory(abseil) +add_subdirectory(boost) +add_subdirectory(bugprone) +add_subdirectory(cert) +add_subdirectory(cppcoreguidelines) +add_subdirectory(fuchsia) +add_subdirectory(google) +add_subdirectory(hicpp) +add_subdirectory(llvm) +add_subdirectory(misc) +add_subdirectory(modernize) +add_subdirectory(mpi) +add_subdirectory(objc) +add_subdirectory(performance) +add_subdirectory(plugin) +add_subdirectory(portability) +add_subdirectory(readability) +add_subdirectory(tool) +add_subdirectory(utils) +add_subdirectory(zircon) diff --git a/clang-tidy/ClangTidy.cpp b/clang-tidy/ClangTidy.cpp new file mode 100644 index 000000000..f497fd749 --- /dev/null +++ b/clang-tidy/ClangTidy.cpp @@ -0,0 +1,621 @@ +//===--- 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 "ClangTidyProfiling.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, + llvm::IntrusiveRefCntPtr BaseFS) + : Files(FileSystemOptions(), BaseFS), 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 Profiling, + std::unique_ptr Finder, + std::vector> Checks) + : MultiplexConsumer(std::move(Consumers)), + Profiling(std::move(Profiling)), Finder(std::move(Finder)), + Checks(std::move(Checks)) {} + +private: + // Destructor order matters! Profiling must be destructed last. + // Or at least after Finder. + std::unique_ptr Profiling; + 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, + bool IncludeExperimental) { + CheckersList List; + + const auto &RegisteredCheckers = + AnalyzerOptions::getRegisteredCheckers(IncludeExperimental); + 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; + + std::unique_ptr Profiling; + if (Context.getEnableProfiling()) { + Profiling = llvm::make_unique( + Context.getProfileStorageParams()); + FinderOptions.CheckProfiling.emplace(Profiling->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(); + AnalyzerOptions->CheckersControlList = + getCheckersControlList(Context, Context.canEnableAnalyzerAlphaCheckers()); + 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(Profiling), 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, Context.canEnableAnalyzerAlphaCheckers())) + 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, + bool AllowEnablingAnalyzerAlphaCheckers) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options), + AllowEnablingAnalyzerAlphaCheckers); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckNames(); +} + +ClangTidyOptions::OptionMap +getCheckOptions(const ClangTidyOptions &Options, + bool AllowEnablingAnalyzerAlphaCheckers) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options), + AllowEnablingAnalyzerAlphaCheckers); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckOptions(); +} + +void runClangTidy(clang::tidy::ClangTidyContext &Context, + const CompilationDatabase &Compilations, + ArrayRef InputFiles, + llvm::IntrusiveRefCntPtr BaseFS, + bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) { + ClangTool Tool(Compilations, InputFiles, + std::make_shared(), BaseFS); + + // 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); + Context.setEnableProfiling(EnableCheckProfile); + Context.setProfileStoragePrefix(StoreCheckProfile); + + ClangTidyDiagnosticConsumer DiagConsumer(Context); + + Tool.setDiagnosticConsumer(&DiagConsumer); + + class ActionFactory : public FrontendActionFactory { + public: + ActionFactory(ClangTidyContext &Context) : ConsumerFactory(Context) {} + FrontendAction *create() override { return new Action(&ConsumerFactory); } + + bool runInvocation(std::shared_ptr Invocation, + FileManager *Files, + std::shared_ptr PCHContainerOps, + DiagnosticConsumer *DiagConsumer) override { + // Explicitly set ProgramAction to RunAnalysis to make the preprocessor + // define __clang_analyzer__ macro. The frontend analyzer action will not + // be called here. + Invocation->getFrontendOpts().ProgramAction = frontend::RunAnalysis; + return FrontendActionFactory::runInvocation( + Invocation, Files, PCHContainerOps, DiagConsumer); + } + + 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, + llvm::IntrusiveRefCntPtr BaseFS) { + ErrorReporter Reporter(Context, Fix, BaseFS); + 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..0ea9a7018 --- /dev/null +++ b/clang-tidy/ClangTidy.h @@ -0,0 +1,259 @@ +//===--- 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, + bool AllowEnablingAnalyzerAlphaCheckers); + +/// \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, + bool AllowEnablingAnalyzerAlphaCheckers); + +/// \brief Run a set of clang-tidy checks on a set of files. +/// +/// \param EnableCheckProfile If provided, it enables check profile collection +/// in MatchFinder, and will contain the result of the profile. +/// \param StoreCheckProfile If provided, and EnableCheckProfile is true, +/// the profile will not be output to stderr, but will instead be stored +/// as a JSON file in the specified directory. +void runClangTidy(clang::tidy::ClangTidyContext &Context, + const tooling::CompilationDatabase &Compilations, + ArrayRef InputFiles, + llvm::IntrusiveRefCntPtr BaseFS, + bool EnableCheckProfile = false, + llvm::StringRef StoreCheckProfile = StringRef()); + +// 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, + llvm::IntrusiveRefCntPtr BaseFS); + +/// \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..a1c76fbac --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -0,0 +1,690 @@ +//===--- 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/STLExtras.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(" \r\n"); + 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, + bool AllowEnablingAnalyzerAlphaCheckers) + : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), + Profile(false), + AllowEnablingAnalyzerAlphaCheckers(AllowEnablingAnalyzerAlphaCheckers) { + // 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::setEnableProfiling(bool P) { Profile = P; } + +void ClangTidyContext::setProfileStoragePrefix(StringRef Prefix) { + ProfilePrefix = Prefix; +} + +llvm::Optional +ClangTidyContext::getProfileStorageParams() const { + if (ProfilePrefix.empty()) + return llvm::None; + + return ClangTidyProfiling::StorageParams(ProfilePrefix, CurrentFile); +} + +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 IsNOLINTFound(StringRef NolintDirectiveText, StringRef Line, + unsigned DiagID, const ClangTidyContext &Context) { + const size_t NolintIndex = Line.find(NolintDirectiveText); + if (NolintIndex == StringRef::npos) + return false; + + size_t BracketIndex = NolintIndex + NolintDirectiveText.size(); + // Check if the specific checks are specified in brackets. + if (BracketIndex < Line.size() && Line[BracketIndex] == '(') { + ++BracketIndex; + const size_t BracketEndIndex = Line.find(')', BracketIndex); + if (BracketEndIndex != StringRef::npos) { + StringRef ChecksStr = + Line.substr(BracketIndex, BracketEndIndex - BracketIndex); + // Allow disabling all the checks with "*". + if (ChecksStr != "*") { + StringRef CheckName = Context.getCheckName(DiagID); + // Allow specifying a few check names, delimited with comma. + SmallVector Checks; + ChecksStr.split(Checks, ',', -1, false); + llvm::transform(Checks, Checks.begin(), + [](StringRef S) { return S.trim(); }); + return llvm::find(Checks, CheckName) != Checks.end(); + } + } + } + return true; +} + +static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc, + unsigned DiagID, + const ClangTidyContext &Context) { + 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); + if (IsNOLINTFound("NOLINT", RestOfLine, DiagID, Context)) + 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 (IsNOLINTFound("NOLINTNEXTLINE", RestOfLine, DiagID, Context)) + return true; + + return false; +} + +static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, SourceLocation Loc, + unsigned DiagID, + const ClangTidyContext &Context) { + while (true) { + if (LineIsMarkedWithNOLINT(SM, Loc, DiagID, Context)) + return true; + if (!Loc.isMacroID()) + return false; + Loc = SM.getImmediateExpansionRange(Loc).getBegin(); + } + 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(), Info.getID(), + Context)) { + ++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..ae25013da --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -0,0 +1,276 @@ +//===--- 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 "ClangTidyProfiling.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 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, + bool AllowEnablingAnalyzerAlphaCheckers = false); + + ~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 Control profile collection in clang-tidy. + void setEnableProfiling(bool Profile); + bool getEnableProfiling() const { return Profile; } + + /// \brief Control storage of profile date. + void setProfileStoragePrefix(StringRef ProfilePrefix); + llvm::Optional + getProfileStorageParams() const; + + /// \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; + } + + /// \brief If the experimental alpha checkers from the static analyzer can be + /// enabled. + bool canEnableAnalyzerAlphaCheckers() const { + return AllowEnablingAnalyzerAlphaCheckers; + } + +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; + + bool Profile; + std::string ProfilePrefix; + + bool AllowEnablingAnalyzerAlphaCheckers; +}; + +/// \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..430f8b31e --- /dev/null +++ b/clang-tidy/ClangTidyOptions.cpp @@ -0,0 +1,349 @@ +//===--- 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); + bool Ignored = false; + IO.mapOptional("Checks", Options.Checks); + IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors); + IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); + IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // legacy compatibility + 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.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.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, + llvm::IntrusiveRefCntPtr VFS) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions), FS(std::move(VFS)) { + if (!FS) + FS = vfs::getRealFileSystem(); + 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) { + LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName + << "...\n"); + assert(FS && "FS must be set."); + + llvm::SmallString<128> AbsoluteFilePath(FileName); + + if (FS->makeAbsolute(AbsoluteFilePath)) + return {}; + + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str()); + 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(AbsoluteFilePath.str()); + 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) { + LLVM_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); + LLVM_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..b2a4ce4db --- /dev/null +++ b/clang-tidy/ClangTidyOptions.h @@ -0,0 +1,275 @@ +//===--- 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/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/ErrorOr.h" +#include "clang/Basic/VirtualFileSystem.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 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, + llvm::IntrusiveRefCntPtr FS = nullptr); + + /// \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; + llvm::IntrusiveRefCntPtr FS; +}; + +/// \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/ClangTidyProfiling.cpp b/clang-tidy/ClangTidyProfiling.cpp new file mode 100644 index 000000000..fc0a9697b --- /dev/null +++ b/clang-tidy/ClangTidyProfiling.cpp @@ -0,0 +1,93 @@ +//===--- ClangTidyProfiling.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 "ClangTidyProfiling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +#define DEBUG_TYPE "clang-tidy-profiling" + +namespace clang { +namespace tidy { + +ClangTidyProfiling::StorageParams::StorageParams(llvm::StringRef ProfilePrefix, + llvm::StringRef SourceFile) + : Timestamp(std::chrono::system_clock::now()), SourceFilename(SourceFile) { + llvm::SmallString<32> TimestampStr; + llvm::raw_svector_ostream OS(TimestampStr); + llvm::format_provider::format(Timestamp, OS, + "%Y%m%d%H%M%S%N"); + + llvm::SmallString<256> FinalPrefix(ProfilePrefix); + llvm::sys::path::append(FinalPrefix, TimestampStr); + + // So the full output name is: /ProfilePrefix/timestamp-inputfilename.json + StoreFilename = llvm::Twine(FinalPrefix + "-" + + llvm::sys::path::filename(SourceFile) + ".json") + .str(); +} + +void ClangTidyProfiling::printUserFriendlyTable(llvm::raw_ostream &OS) { + TG->print(OS); + OS.flush(); +} + +void ClangTidyProfiling::printAsJSON(llvm::raw_ostream &OS) { + OS << "{\n"; + OS << "\"file\": \"" << Storage->SourceFilename << "\",\n"; + OS << "\"timestamp\": \"" << Storage->Timestamp << "\",\n"; + OS << "\"profile\": {\n"; + TG->printJSONValues(OS, ""); + OS << "\n}\n"; + OS << "}\n"; + OS.flush(); +} + +void ClangTidyProfiling::storeProfileData() { + assert(Storage.hasValue() && "We should have a filename."); + + llvm::SmallString<256> OutputDirectory(Storage->StoreFilename); + llvm::sys::path::remove_filename(OutputDirectory); + if (std::error_code EC = llvm::sys::fs::create_directories(OutputDirectory)) { + llvm::errs() << "Unable to create output directory '" << OutputDirectory + << "': " << EC.message() << "\n"; + return; + } + + std::error_code EC; + llvm::raw_fd_ostream OS(Storage->StoreFilename, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Error opening output file '" << Storage->StoreFilename + << "': " << EC.message() << "\n"; + return; + } + + printAsJSON(OS); +} + +ClangTidyProfiling::ClangTidyProfiling(llvm::Optional Storage) + : Storage(std::move(Storage)) {} + +ClangTidyProfiling::~ClangTidyProfiling() { + TG.emplace("clang-tidy", "clang-tidy checks profiling", Records); + + if (!Storage.hasValue()) + printUserFriendlyTable(llvm::errs()); + else + storeProfileData(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidyProfiling.h b/clang-tidy/ClangTidyProfiling.h new file mode 100644 index 000000000..9d86b8e9e --- /dev/null +++ b/clang-tidy/ClangTidyProfiling.h @@ -0,0 +1,60 @@ +//===--- ClangTidyProfiling.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_CLANGTIDYPROFILING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/Timer.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace clang { +namespace tidy { + +class ClangTidyProfiling { +public: + struct StorageParams { + llvm::sys::TimePoint<> Timestamp; + std::string SourceFilename; + std::string StoreFilename; + + StorageParams() = default; + + StorageParams(llvm::StringRef ProfilePrefix, llvm::StringRef SourceFile); + }; + +private: + llvm::Optional TG; + + llvm::Optional Storage; + + void printUserFriendlyTable(llvm::raw_ostream &OS); + void printAsJSON(llvm::raw_ostream &OS); + + void storeProfileData(); + +public: + llvm::StringMap Records; + + ClangTidyProfiling() = default; + + ClangTidyProfiling(llvm::Optional Storage); + + ~ClangTidyProfiling(); +}; + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYPROFILING_H diff --git a/clang-tidy/abseil/AbseilTidyModule.cpp b/clang-tidy/abseil/AbseilTidyModule.cpp new file mode 100644 index 000000000..8e427f058 --- /dev/null +++ b/clang-tidy/abseil/AbseilTidyModule.cpp @@ -0,0 +1,38 @@ +//===------- AbseilTidyModule.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 "StringFindStartswithCheck.h" + +namespace clang { +namespace tidy { +namespace abseil { + +class AbseilModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "abseil-string-find-startswith"); + } +}; + +// Register the AbseilModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("abseil-module", + "Add Abseil checks."); + +} // namespace abseil + +// This anchor is used to force the linker to link in the generated object file +// and thus register the AbseilModule. +volatile int AbseilModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/abseil/CMakeLists.txt b/clang-tidy/abseil/CMakeLists.txt new file mode 100644 index 000000000..dd59dcf0c --- /dev/null +++ b/clang-tidy/abseil/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyAbseilModule + AbseilTidyModule.cpp + StringFindStartswithCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/abseil/StringFindStartswithCheck.cpp b/clang-tidy/abseil/StringFindStartswithCheck.cpp new file mode 100644 index 000000000..7a94c1055 --- /dev/null +++ b/clang-tidy/abseil/StringFindStartswithCheck.cpp @@ -0,0 +1,136 @@ +//===--- StringFindStartswithCheck.cc - 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 "StringFindStartswithCheck.h" + +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace abseil { + +StringFindStartswithCheck::StringFindStartswithCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StringLikeClasses(utils::options::parseStringList( + Options.get("StringLikeClasses", "::std::basic_string"))), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.getLocalOrGlobal("IncludeStyle", "llvm"))), + AbseilStringsMatchHeader( + Options.get("AbseilStringsMatchHeader", "absl/strings/match.h")) {} + +void StringFindStartswithCheck::registerMatchers(MatchFinder *Finder) { + auto ZeroLiteral = integerLiteral(equals(0)); + auto StringClassMatcher = cxxRecordDecl(hasAnyName(SmallVector( + StringLikeClasses.begin(), StringLikeClasses.end()))); + auto StringType = hasUnqualifiedDesugaredType( + recordType(hasDeclaration(StringClassMatcher))); + + auto StringFind = cxxMemberCallExpr( + // .find()-call on a string... + callee(cxxMethodDecl(hasName("find"))), + on(hasType(StringType)), + // ... with some search expression ... + hasArgument(0, expr().bind("needle")), + // ... and either "0" as second argument or the default argument (also 0). + anyOf(hasArgument(1, ZeroLiteral), hasArgument(1, cxxDefaultArgExpr()))); + + Finder->addMatcher( + // Match [=!]= with a zero on one side and a string.find on the other. + binaryOperator( + anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(ignoringParenImpCasts(ZeroLiteral)), + hasEitherOperand(ignoringParenImpCasts(StringFind.bind("findexpr")))) + .bind("expr"), + this); +} + +void StringFindStartswithCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Context = *Result.Context; + const SourceManager &Source = Context.getSourceManager(); + + // Extract matching (sub)expressions + const auto *ComparisonExpr = Result.Nodes.getNodeAs("expr"); + assert(ComparisonExpr != nullptr); + const auto *Needle = Result.Nodes.getNodeAs("needle"); + assert(Needle != nullptr); + const Expr *Haystack = Result.Nodes.getNodeAs("findexpr") + ->getImplicitObjectArgument(); + assert(Haystack != nullptr); + + if (ComparisonExpr->getLocStart().isMacroID()) + return; + + // Get the source code blocks (as characters) for both the string object + // and the search expression + const StringRef NeedleExprCode = Lexer::getSourceText( + CharSourceRange::getTokenRange(Needle->getSourceRange()), Source, + Context.getLangOpts()); + const StringRef HaystackExprCode = Lexer::getSourceText( + CharSourceRange::getTokenRange(Haystack->getSourceRange()), Source, + Context.getLangOpts()); + + // Create the StartsWith string, negating if comparison was "!=". + bool Neg = ComparisonExpr->getOpcodeStr() == "!="; + StringRef StartswithStr; + if (Neg) { + StartswithStr = "!absl::StartsWith"; + } else { + StartswithStr = "absl::StartsWith"; + } + + // Create the warning message and a FixIt hint replacing the original expr. + auto Diagnostic = + diag(ComparisonExpr->getLocStart(), + (StringRef("use ") + StartswithStr + " instead of find() " + + ComparisonExpr->getOpcodeStr() + " 0") + .str()); + + Diagnostic << FixItHint::CreateReplacement( + ComparisonExpr->getSourceRange(), + (StartswithStr + "(" + HaystackExprCode + ", " + NeedleExprCode + ")") + .str()); + + // Create a preprocessor #include FixIt hint (CreateIncludeInsertion checks + // whether this already exists). + auto IncludeHint = IncludeInserter->CreateIncludeInsertion( + Source.getFileID(ComparisonExpr->getLocStart()), AbseilStringsMatchHeader, + false); + if (IncludeHint) { + Diagnostic << *IncludeHint; + } +} + +void StringFindStartswithCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + IncludeInserter = llvm::make_unique( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle); + Compiler.getPreprocessor().addPPCallbacks( + IncludeInserter->CreatePPCallbacks()); +} + +void StringFindStartswithCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StringLikeClasses", + utils::options::serializeStringList(StringLikeClasses)); + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); + Options.store(Opts, "AbseilStringsMatchHeader", AbseilStringsMatchHeader); +} + +} // namespace abseil +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/abseil/StringFindStartswithCheck.h b/clang-tidy/abseil/StringFindStartswithCheck.h new file mode 100644 index 000000000..1c04e805a --- /dev/null +++ b/clang-tidy/abseil/StringFindStartswithCheck.h @@ -0,0 +1,48 @@ +//===--- StringFindStartswithCheck.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_ABSEIL_STRINGFINDSTARTSWITHCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRINGFINDSTARTSWITHCHECK_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include +#include +#include + +namespace clang { +namespace tidy { +namespace abseil { + +// Find string.find(...) == 0 comparisons and suggest replacing with StartsWith. +// FIXME(niko): Add similar check for EndsWith +// FIXME(niko): Add equivalent modernize checks for C++20's std::starts_With +class StringFindStartswithCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + StringFindStartswithCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + std::unique_ptr IncludeInserter; + const std::vector StringLikeClasses; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + const std::string AbseilStringsMatchHeader; +}; + +} // namespace abseil +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ABSEIL_STRINGFINDSTARTSWITHCHECK_H diff --git a/clang-tidy/add_new_check.py b/clang-tidy/add_new_check.py new file mode 100755 index 000000000..4d811aa1f --- /dev/null +++ b/clang-tidy/add_new_check.py @@ -0,0 +1,368 @@ +#!/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. +# +#===------------------------------------------------------------------------===# + +from __future__ import print_function + +import argparse +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, 'w') 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, 'w') as f: + header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_' + + check_name_camel.upper() + '_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, 'w') 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 = list(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, 'w') 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, 'w') 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 :doc:`%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, test_extension): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy', + check_name_dashes + '.' + test_extension)) + print('Creating %s...' % filename) + with open(filename, 'w') 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 = list(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, 'w') 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, 'w') 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(): + language_to_extension = { + 'c': 'c', + 'c++': 'cpp', + 'objc': 'm', + 'objc++': 'mm', + } + parser = argparse.ArgumentParser() + parser.add_argument( + '--update-docs', + action='store_true', + help='just update the list of documentation files, then exit') + parser.add_argument( + '--language', + help='language to use for new check (defaults to c++)', + choices=language_to_extension.keys(), + default='c++', + metavar='LANG') + parser.add_argument( + 'module', + nargs='?', + help='module directory under which to place the new tidy check (e.g., misc)') + parser.add_argument( + 'check', + nargs='?', + help='name of new tidy check to add (e.g. foo-do-the-stuff)') + args = parser.parse_args() + + if args.update_docs: + update_checks_list(os.path.dirname(sys.argv[0])) + return + + if not args.module or not args.check: + print('Module and check must be specified.') + parser.print_usage() + return + + module = args.module + check_name = args.check + + 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) + test_extension = language_to_extension.get(args.language) + write_test(module_path, module, check_name, test_extension) + 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..64d35c701 --- /dev/null +++ b/clang-tidy/android/AndroidTidyModule.cpp @@ -0,0 +1,70 @@ +//===--- 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 "CloexecAccept4Check.h" +#include "CloexecAcceptCheck.h" +#include "CloexecCreatCheck.h" +#include "CloexecDupCheck.h" +#include "CloexecEpollCreate1Check.h" +#include "CloexecEpollCreateCheck.h" +#include "CloexecFopenCheck.h" +#include "CloexecInotifyInit1Check.h" +#include "CloexecInotifyInitCheck.h" +#include "CloexecMemfdCreateCheck.h" +#include "CloexecOpenCheck.h" +#include "CloexecSocketCheck.h" +#include "ComparisonInTempFailureRetryCheck.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-accept4"); + CheckFactories.registerCheck("android-cloexec-accept"); + CheckFactories.registerCheck("android-cloexec-creat"); + CheckFactories.registerCheck( + "android-cloexec-epoll-create1"); + CheckFactories.registerCheck( + "android-cloexec-epoll-create"); + CheckFactories.registerCheck("android-cloexec-dup"); + CheckFactories.registerCheck("android-cloexec-fopen"); + CheckFactories.registerCheck( + "android-cloexec-inotify-init"); + CheckFactories.registerCheck( + "android-cloexec-inotify-init1"); + CheckFactories.registerCheck( + "android-cloexec-memfd-create"); + CheckFactories.registerCheck("android-cloexec-open"); + CheckFactories.registerCheck("android-cloexec-socket"); + CheckFactories.registerCheck( + "android-comparison-in-temp-failure-retry"); + } +}; + +// 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..37aebacf2 --- /dev/null +++ b/clang-tidy/android/CMakeLists.txt @@ -0,0 +1,27 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyAndroidModule + AndroidTidyModule.cpp + CloexecAccept4Check.cpp + CloexecAcceptCheck.cpp + CloexecCheck.cpp + CloexecCreatCheck.cpp + CloexecEpollCreate1Check.cpp + CloexecEpollCreateCheck.cpp + CloexecDupCheck.cpp + CloexecFopenCheck.cpp + CloexecInotifyInit1Check.cpp + CloexecInotifyInitCheck.cpp + CloexecMemfdCreateCheck.cpp + CloexecOpenCheck.cpp + CloexecSocketCheck.cpp + ComparisonInTempFailureRetryCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/android/CloexecAccept4Check.cpp b/clang-tidy/android/CloexecAccept4Check.cpp new file mode 100644 index 000000000..3d172ef36 --- /dev/null +++ b/clang-tidy/android/CloexecAccept4Check.cpp @@ -0,0 +1,40 @@ +//===--- CloexecAccept4Check.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 "CloexecAccept4Check.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 { + +void CloexecAccept4Check::registerMatchers(MatchFinder *Finder) { + auto SockAddrPointerType = + hasType(pointsTo(recordDecl(isStruct(), hasName("sockaddr")))); + auto SockLenPointerType = hasType(pointsTo(namedDecl(hasName("socklen_t")))); + + registerMatchersImpl(Finder, + functionDecl(returns(isInteger()), hasName("accept4"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, SockAddrPointerType), + hasParameter(2, SockLenPointerType), + hasParameter(3, hasType(isInteger())))); +} + +void CloexecAccept4Check::check(const MatchFinder::MatchResult &Result) { + insertMacroFlag(Result, /*MacroFlag=*/"SOCK_CLOEXEC", /*ArgPos=*/3); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecAccept4Check.h b/clang-tidy/android/CloexecAccept4Check.h new file mode 100644 index 000000000..0cf32152d --- /dev/null +++ b/clang-tidy/android/CloexecAccept4Check.h @@ -0,0 +1,35 @@ +//===--- CloexecAccept4Check.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_ACCEPT4_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_ACCEPT4_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Finds code that uses accept4() without using the SOCK_CLOEXEC flag. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-accept4.html +class CloexecAccept4Check : public CloexecCheck { +public: + CloexecAccept4Check(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_ACCEPT4_H diff --git a/clang-tidy/android/CloexecAcceptCheck.cpp b/clang-tidy/android/CloexecAcceptCheck.cpp new file mode 100644 index 000000000..f583bd0c6 --- /dev/null +++ b/clang-tidy/android/CloexecAcceptCheck.cpp @@ -0,0 +1,47 @@ +//===--- CloexecAcceptCheck.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 "CloexecAcceptCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecAcceptCheck::registerMatchers(MatchFinder *Finder) { + auto SockAddrPointerType = + hasType(pointsTo(recordDecl(isStruct(), hasName("sockaddr")))); + auto SockLenPointerType = hasType(pointsTo(namedDecl(hasName("socklen_t")))); + + registerMatchersImpl(Finder, + functionDecl(returns(isInteger()), hasName("accept"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, SockAddrPointerType), + hasParameter(2, SockLenPointerType))); +} + +void CloexecAcceptCheck::check(const MatchFinder::MatchResult &Result) { + const std::string &ReplacementText = + (Twine("accept4(") + getSpellingArg(Result, 0) + ", " + + getSpellingArg(Result, 1) + ", " + getSpellingArg(Result, 2) + + ", SOCK_CLOEXEC)") + .str(); + + replaceFunc( + Result, + "prefer accept4() to accept() because accept4() allows SOCK_CLOEXEC", + ReplacementText); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecAcceptCheck.h b/clang-tidy/android/CloexecAcceptCheck.h new file mode 100644 index 000000000..cba1c0983 --- /dev/null +++ b/clang-tidy/android/CloexecAcceptCheck.h @@ -0,0 +1,35 @@ +//===--- CloexecAcceptCheck.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_ACCEPT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_ACCEPT_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// accept() is better to be replaced by accept4(). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-accept.html +class CloexecAcceptCheck : public CloexecCheck { +public: + CloexecAcceptCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_ACCEPT_H diff --git a/clang-tidy/android/CloexecCheck.cpp b/clang-tidy/android/CloexecCheck.cpp new file mode 100644 index 000000000..8473f1ab8 --- /dev/null +++ b/clang-tidy/android/CloexecCheck.cpp @@ -0,0 +1,114 @@ +//===--- CloexecCheck.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 "CloexecCheck.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 { + +namespace { +// Helper function to form the correct string mode for Type3. +// Build the replace text. If it's string constant, add directly in the +// end of the string. Else, add . +std::string buildFixMsgForStringFlag(const Expr *Arg, const SourceManager &SM, + const LangOptions &LangOpts, char Mode) { + 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 + +const char *CloexecCheck::FuncDeclBindingStr = "funcDecl"; + +const char *CloexecCheck::FuncBindingStr ="func"; + +void CloexecCheck::registerMatchersImpl( + MatchFinder *Finder, internal::Matcher Function) { + // We assume all the checked APIs are C functions. + Finder->addMatcher( + callExpr( + callee(functionDecl(isExternC(), Function).bind(FuncDeclBindingStr))) + .bind(FuncBindingStr), + this); +} + +void CloexecCheck::insertMacroFlag(const MatchFinder::MatchResult &Result, + StringRef MacroFlag, int ArgPos) { + const auto *MatchedCall = Result.Nodes.getNodeAs(FuncBindingStr); + const auto *FlagArg = MatchedCall->getArg(ArgPos); + const auto *FD = Result.Nodes.getNodeAs(FuncDeclBindingStr); + SourceManager &SM = *Result.SourceManager; + + if (utils::exprHasBitFlagWithSpelling(FlagArg->IgnoreParenCasts(), SM, + Result.Context->getLangOpts(), + MacroFlag)) + return; + + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(SM.getFileLoc(FlagArg->getLocEnd()), 0, SM, + Result.Context->getLangOpts()); + + diag(EndLoc, "%0 should use %1 where possible") + << FD << MacroFlag + << FixItHint::CreateInsertion(EndLoc, (Twine(" | ") + MacroFlag).str()); +} + +void CloexecCheck::replaceFunc(const MatchFinder::MatchResult &Result, + StringRef WarningMsg, StringRef FixMsg) { + const auto *MatchedCall = Result.Nodes.getNodeAs(FuncBindingStr); + diag(MatchedCall->getLocStart(), WarningMsg) + << FixItHint::CreateReplacement(MatchedCall->getSourceRange(), FixMsg); +} + +void CloexecCheck::insertStringFlag( + const ast_matchers::MatchFinder::MatchResult &Result, const char Mode, + const int ArgPos) { + const auto *MatchedCall = Result.Nodes.getNodeAs(FuncBindingStr); + const auto *FD = Result.Nodes.getNodeAs(FuncDeclBindingStr); + const auto *ModeArg = MatchedCall->getArg(ArgPos); + + // Check if the 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 = buildFixMsgForStringFlag( + ModeArg, *Result.SourceManager, Result.Context->getLangOpts(), Mode); + + diag(ModeArg->getLocStart(), "use %0 mode '%1' to set O_CLOEXEC") + << FD << std::string(1, Mode) + << FixItHint::CreateReplacement(ModeArg->getSourceRange(), + ReplacementText); +} + +StringRef CloexecCheck::getSpellingArg(const MatchFinder::MatchResult &Result, + int N) const { + const auto *MatchedCall = Result.Nodes.getNodeAs(FuncBindingStr); + const SourceManager &SM = *Result.SourceManager; + return Lexer::getSourceText( + CharSourceRange::getTokenRange(MatchedCall->getArg(N)->getSourceRange()), + SM, Result.Context->getLangOpts()); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecCheck.h b/clang-tidy/android/CloexecCheck.h new file mode 100644 index 000000000..1c58a3136 --- /dev/null +++ b/clang-tidy/android/CloexecCheck.h @@ -0,0 +1,105 @@ +//===--- CloexecCheck.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. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declaration of the CloexecCheck class, which is the +/// base class for all of the close-on-exec checks in Android module. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace android { + +/// \brief The base class for all close-on-exec checks in Android module. +/// To be specific, there are some functions that need the close-on-exec flag to +/// prevent the file descriptor leakage on fork+exec and this class provides +/// utilities to identify and fix these C functions. +class CloexecCheck : public ClangTidyCheck { +public: + CloexecCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +protected: + void + registerMatchersImpl(ast_matchers::MatchFinder *Finder, + ast_matchers::internal::Matcher Function); + + /// Currently, we have three types of fixes. + /// + /// Type1 is to insert the necessary macro flag in the flag argument. For + /// example, 'O_CLOEXEC' is required in function 'open()', so + /// \code + /// open(file, O_RDONLY); + /// \endcode + /// should be + /// \code + /// open(file, O_RDONLY | O_CLOEXE); + /// \endcode + /// + /// \param [out] Result MatchResult from AST matcher. + /// \param MacroFlag The macro name of the flag. + /// \param ArgPos The 0-based position of the flag argument. + void insertMacroFlag(const ast_matchers::MatchFinder::MatchResult &Result, + StringRef MacroFlag, int ArgPos); + + /// Type2 is to replace the API to another function that has required the + /// ability. For example: + /// \code + /// creat(path, mode); + /// \endcode + /// should be + /// \code + /// open(path, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, mode) + /// \endcode + /// + /// \param [out] Result MatchResult from AST matcher. + /// \param WarningMsg The warning message. + /// \param FixMsg The fix message. + void replaceFunc(const ast_matchers::MatchFinder::MatchResult &Result, + StringRef WarningMsg, StringRef FixMsg); + + /// Type3 is also to add a flag to the corresponding argument, but this time, + /// the flag is some string and each char represents a mode rather than a + /// macro. For example, 'fopen' needs char 'e' in its mode argument string, so + /// \code + /// fopen(in_file, "r"); + /// \endcode + /// should be + /// \code + /// fopen(in_file, "re"); + /// \endcode + /// + /// \param [out] Result MatchResult from AST matcher. + /// \param Mode The required mode char. + /// \param ArgPos The 0-based position of the flag argument. + void insertStringFlag(const ast_matchers::MatchFinder::MatchResult &Result, + const char Mode, const int ArgPos); + + /// Helper function to get the spelling of a particular argument. + StringRef getSpellingArg(const ast_matchers::MatchFinder::MatchResult &Result, + int N) const; + + /// Binding name of the FuncDecl of a function call. + static const char *FuncDeclBindingStr; + + /// Binding name of the function call expression. + static const char *FuncBindingStr; +}; + +} // namespace android +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_H diff --git a/clang-tidy/android/CloexecCreatCheck.cpp b/clang-tidy/android/CloexecCreatCheck.cpp new file mode 100644 index 000000000..83ca49a31 --- /dev/null +++ b/clang-tidy/android/CloexecCreatCheck.cpp @@ -0,0 +1,43 @@ +//===--- 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" + +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"))); + registerMatchersImpl(Finder, + functionDecl(isExternC(), returns(isInteger()), + hasName("creat"), + hasParameter(0, CharPointerType), + hasParameter(1, MODETType))); +} + +void CloexecCreatCheck::check(const MatchFinder::MatchResult &Result) { + const std::string &ReplacementText = + (Twine("open (") + getSpellingArg(Result, 0) + + ", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, " + + getSpellingArg(Result, 1) + ")") + .str(); + replaceFunc(Result, + "prefer open() to creat() because open() allows O_CLOEXEC", + 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..335990cce --- /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 "CloexecCheck.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 CloexecCheck { +public: + CloexecCreatCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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/CloexecDupCheck.cpp b/clang-tidy/android/CloexecDupCheck.cpp new file mode 100644 index 000000000..ec4ff0acd --- /dev/null +++ b/clang-tidy/android/CloexecDupCheck.cpp @@ -0,0 +1,38 @@ +//===--- CloexecDupCheck.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 "CloexecDupCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecDupCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersImpl(Finder, + functionDecl(returns(isInteger()), hasName("dup"), + hasParameter(0, hasType(isInteger())))); +} + +void CloexecDupCheck::check(const MatchFinder::MatchResult &Result) { + const std::string &ReplacementText = + (Twine("fcntl(") + getSpellingArg(Result, 0) + ", F_DUPFD_CLOEXEC)") + .str(); + + replaceFunc(Result, + "prefer fcntl() to dup() because fcntl() allows F_DUPFD_CLOEXEC", + ReplacementText); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecDupCheck.h b/clang-tidy/android/CloexecDupCheck.h new file mode 100644 index 000000000..c040b3808 --- /dev/null +++ b/clang-tidy/android/CloexecDupCheck.h @@ -0,0 +1,36 @@ +//===--- CloexecDupCheck.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_DUP_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_DUP_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// dup() is better to be replaced by fcntl(), which has close-on-exec flag. +/// Find the usage of dup() and redirect user to use fcntl(). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-dup.html +class CloexecDupCheck : public CloexecCheck { +public: + CloexecDupCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_DUP_H diff --git a/clang-tidy/android/CloexecEpollCreate1Check.cpp b/clang-tidy/android/CloexecEpollCreate1Check.cpp new file mode 100644 index 000000000..d94b4a06a --- /dev/null +++ b/clang-tidy/android/CloexecEpollCreate1Check.cpp @@ -0,0 +1,33 @@ +//===--- CloexecEpollCreate1Check.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 "CloexecEpollCreate1Check.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 { + +void CloexecEpollCreate1Check::registerMatchers(MatchFinder *Finder) { + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("epoll_create1"), + hasParameter(0, hasType(isInteger())))); +} + +void CloexecEpollCreate1Check::check(const MatchFinder::MatchResult &Result) { + insertMacroFlag(Result, /*MacroFlag=*/"EPOLL_CLOEXEC", /*ArgPos=*/0); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecEpollCreate1Check.h b/clang-tidy/android/CloexecEpollCreate1Check.h new file mode 100644 index 000000000..9890d5f0b --- /dev/null +++ b/clang-tidy/android/CloexecEpollCreate1Check.h @@ -0,0 +1,35 @@ +//===--- CloexecEpollCreate1Check.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_EPOLL_CREATE1_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_EPOLL_CREATE1_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Finds code that uses epoll_create1() without using the EPOLL_CLOEXEC flag. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-epoll-create1.html +class CloexecEpollCreate1Check : public CloexecCheck { +public: + CloexecEpollCreate1Check(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_EPOLL_CREATE1_H diff --git a/clang-tidy/android/CloexecEpollCreateCheck.cpp b/clang-tidy/android/CloexecEpollCreateCheck.cpp new file mode 100644 index 000000000..7165d24f5 --- /dev/null +++ b/clang-tidy/android/CloexecEpollCreateCheck.cpp @@ -0,0 +1,36 @@ +//===--- CloexecEpollCreateCheck.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 "CloexecEpollCreateCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecEpollCreateCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("epoll_create"), + hasParameter(0, hasType(isInteger())))); +} + +void CloexecEpollCreateCheck::check(const MatchFinder::MatchResult &Result) { + replaceFunc(Result, + "prefer epoll_create() to epoll_create1() " + "because epoll_create1() allows " + "EPOLL_CLOEXEC", + "epoll_create1(EPOLL_CLOEXEC)"); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecEpollCreateCheck.h b/clang-tidy/android/CloexecEpollCreateCheck.h new file mode 100644 index 000000000..21d2b2ab3 --- /dev/null +++ b/clang-tidy/android/CloexecEpollCreateCheck.h @@ -0,0 +1,35 @@ +//===--- CloexecEpollCreateCheck.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_EPOLL_CREATE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_EPOLL_CREATE_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// epoll_create() is better to be replaced by epoll_create1(). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-epoll-create.html +class CloexecEpollCreateCheck : public CloexecCheck { +public: + CloexecEpollCreateCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_EPOLL_CREATE_H diff --git a/clang-tidy/android/CloexecFopenCheck.cpp b/clang-tidy/android/CloexecFopenCheck.cpp new file mode 100644 index 000000000..33058d6e5 --- /dev/null +++ b/clang-tidy/android/CloexecFopenCheck.cpp @@ -0,0 +1,36 @@ +//===--- 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 { + +void CloexecFopenCheck::registerMatchers(MatchFinder *Finder) { + auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); + registerMatchersImpl(Finder, + functionDecl(isExternC(), returns(asString("FILE *")), + hasName("fopen"), + hasParameter(0, CharPointerType), + hasParameter(1, CharPointerType))); +} + +void CloexecFopenCheck::check(const MatchFinder::MatchResult &Result) { + insertStringFlag(Result, /*Mode=*/'e', /*ArgPos=*/1); +} + +} // 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..1c82b8bda --- /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 "CloexecCheck.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 CloexecCheck { +public: + CloexecFopenCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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/CloexecInotifyInit1Check.cpp b/clang-tidy/android/CloexecInotifyInit1Check.cpp new file mode 100644 index 000000000..ca0aeb875 --- /dev/null +++ b/clang-tidy/android/CloexecInotifyInit1Check.cpp @@ -0,0 +1,33 @@ +//===--- CloexecInotifyInit1Check.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 "CloexecInotifyInit1Check.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 { + +void CloexecInotifyInit1Check::registerMatchers(MatchFinder *Finder) { + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("inotify_init1"), + hasParameter(0, hasType(isInteger())))); +} + +void CloexecInotifyInit1Check::check(const MatchFinder::MatchResult &Result) { + insertMacroFlag(Result, /*MacroFlag=*/"IN_CLOEXEC", /*ArgPos=*/0); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecInotifyInit1Check.h b/clang-tidy/android/CloexecInotifyInit1Check.h new file mode 100644 index 000000000..deb04f2a3 --- /dev/null +++ b/clang-tidy/android/CloexecInotifyInit1Check.h @@ -0,0 +1,35 @@ +//===--- CloexecInotifyInit1Check.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_INOTIFY_INIT1_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_INOTIFY_INIT1_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Finds code that uses inotify_init1() without using the IN_CLOEXEC flag. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-inotify-init1.html +class CloexecInotifyInit1Check : public CloexecCheck { +public: + CloexecInotifyInit1Check(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_INOTIFY_INIT1_H diff --git a/clang-tidy/android/CloexecInotifyInitCheck.cpp b/clang-tidy/android/CloexecInotifyInitCheck.cpp new file mode 100644 index 000000000..0d5a56daa --- /dev/null +++ b/clang-tidy/android/CloexecInotifyInitCheck.cpp @@ -0,0 +1,34 @@ +//===--- CloexecInotifyInitCheck.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 "CloexecInotifyInitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecInotifyInitCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("inotify_init"))); +} + +void CloexecInotifyInitCheck::check(const MatchFinder::MatchResult &Result) { + replaceFunc(Result, /*WarningMsg=*/ + "prefer inotify_init() to inotify_init1() " + "because inotify_init1() allows IN_CLOEXEC", + /*FixMsg=*/"inotify_init1(IN_CLOEXEC)"); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecInotifyInitCheck.h b/clang-tidy/android/CloexecInotifyInitCheck.h new file mode 100644 index 000000000..ceeecbea5 --- /dev/null +++ b/clang-tidy/android/CloexecInotifyInitCheck.h @@ -0,0 +1,35 @@ +//===--- CloexecInotifyInitCheck.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_INOTIFY_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_INOTIFY_INIT_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// inotify_init() is better to be replaced by inotify_init1(). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-inotify-init.html +class CloexecInotifyInitCheck : public CloexecCheck { +public: + CloexecInotifyInitCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_INOTIFY_INIT_H diff --git a/clang-tidy/android/CloexecMemfdCreateCheck.cpp b/clang-tidy/android/CloexecMemfdCreateCheck.cpp new file mode 100644 index 000000000..4820e2bdc --- /dev/null +++ b/clang-tidy/android/CloexecMemfdCreateCheck.cpp @@ -0,0 +1,32 @@ +//===--- CloexecMemfdCreateCheck.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 "CloexecMemfdCreateCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecMemfdCreateCheck::registerMatchers(MatchFinder *Finder) { + auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); + registerMatchersImpl( + Finder, functionDecl(returns(isInteger()), hasName("memfd_create"), + hasParameter(0, CharPointerType), + hasParameter(1, hasType(isInteger())))); +} + +void CloexecMemfdCreateCheck::check(const MatchFinder::MatchResult &Result) { + insertMacroFlag(Result, "MFD_CLOEXEC", /*ArgPos=*/1); +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/CloexecMemfdCreateCheck.h b/clang-tidy/android/CloexecMemfdCreateCheck.h new file mode 100644 index 000000000..0fe96f46d --- /dev/null +++ b/clang-tidy/android/CloexecMemfdCreateCheck.h @@ -0,0 +1,35 @@ +//===--- CloexecMemfdCreateCheck.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_MEMFD_CREATE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_CLOEXEC_MEMFD_CREATE_H + +#include "CloexecCheck.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Finds code that uses memfd_create() without using the MFD_CLOEXEC flag. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/android-cloexec-memfd-create.html +class CloexecMemfdCreateCheck : public CloexecCheck { +public: + CloexecMemfdCreateCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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_MEMFD_CREATE_H diff --git a/clang-tidy/android/CloexecOpenCheck.cpp b/clang-tidy/android/CloexecOpenCheck.cpp new file mode 100644 index 000000000..a4d3bc68f --- /dev/null +++ b/clang-tidy/android/CloexecOpenCheck.cpp @@ -0,0 +1,44 @@ +//===--- 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 "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecOpenCheck::registerMatchers(MatchFinder *Finder) { + auto CharPointerType = hasType(pointerType(pointee(isAnyCharacter()))); + registerMatchersImpl(Finder, + functionDecl(isExternC(), returns(isInteger()), + hasAnyName("open", "open64"), + hasParameter(0, CharPointerType), + hasParameter(1, hasType(isInteger())))); + registerMatchersImpl(Finder, + functionDecl(isExternC(), returns(isInteger()), + hasName("openat"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, CharPointerType), + hasParameter(2, hasType(isInteger())))); +} + +void CloexecOpenCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FD = Result.Nodes.getNodeAs(FuncDeclBindingStr); + assert(FD->param_size() > 1); + int ArgPos = (FD->param_size() > 2) ? 2 : 1; + insertMacroFlag(Result, /*MacroFlag=*/"O_CLOEXEC", ArgPos); +} + +} // 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..c221087f9 --- /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 "CloexecCheck.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 CloexecCheck { +public: + CloexecOpenCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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..b223918f1 --- /dev/null +++ b/clang-tidy/android/CloexecSocketCheck.cpp @@ -0,0 +1,35 @@ +//===--- 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 "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace android { + +void CloexecSocketCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersImpl(Finder, + functionDecl(isExternC(), returns(isInteger()), + hasName("socket"), + hasParameter(0, hasType(isInteger())), + hasParameter(1, hasType(isInteger())), + hasParameter(2, hasType(isInteger())))); +} + +void CloexecSocketCheck::check(const MatchFinder::MatchResult &Result) { + insertMacroFlag(Result, /*MacroFlag=*/"SOCK_CLOEXEC", /*ArgPos=*/1); +} + +} // 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..c1fd01f18 --- /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 "CloexecCheck.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 CloexecCheck { +public: + CloexecSocketCheck(StringRef Name, ClangTidyContext *Context) + : CloexecCheck(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/android/ComparisonInTempFailureRetryCheck.cpp b/clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp new file mode 100644 index 000000000..49db53a03 --- /dev/null +++ b/clang-tidy/android/ComparisonInTempFailureRetryCheck.cpp @@ -0,0 +1,84 @@ +//===--- ComparisonInTempFailureRetryCheck.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 "../utils/Matchers.h" +#include "ComparisonInTempFailureRetryCheck.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 { + +namespace { +AST_MATCHER(BinaryOperator, isRHSATempFailureRetryArg) { + if (!Node.getLocStart().isMacroID()) + return false; + + const SourceManager &SM = Finder->getASTContext().getSourceManager(); + if (!SM.isMacroArgExpansion(Node.getRHS()->IgnoreParenCasts()->getLocStart())) + return false; + + const LangOptions &Opts = Finder->getASTContext().getLangOpts(); + SourceLocation LocStart = Node.getLocStart(); + while (LocStart.isMacroID()) { + SourceLocation Invocation = SM.getImmediateMacroCallerLoc(LocStart); + Token Tok; + if (!Lexer::getRawToken(SM.getSpellingLoc(Invocation), Tok, SM, Opts, + /*IgnoreWhiteSpace=*/true)) { + if (Tok.getKind() == tok::raw_identifier && + Tok.getRawIdentifier() == "TEMP_FAILURE_RETRY") + return true; + } + + LocStart = Invocation; + } + return false; +} +} // namespace + +void ComparisonInTempFailureRetryCheck::registerMatchers(MatchFinder *Finder) { + // Both glibc's and Bionic's TEMP_FAILURE_RETRY macros structurally look like: + // + // #define TEMP_FAILURE_RETRY(x) ({ \ + // typeof(x) y; \ + // do y = (x); \ + // while (y == -1 && errno == EINTR); \ + // y; \ + // }) + // + // (glibc uses `long int` instead of `typeof(x)` for the type of y). + // + // It's unclear how to walk up the AST from inside the expansion of `x`, and + // we need to not complain about things like TEMP_FAILURE_RETRY(foo(x == 1)), + // so we just match the assignment of `y = (x)` and inspect `x` from there. + Finder->addMatcher( + binaryOperator( + hasOperatorName("="), + hasRHS(ignoringParenCasts( + binaryOperator(matchers::isComparisonOperator()).bind("binop"))), + isRHSATempFailureRetryArg()), + this); +} + +void ComparisonInTempFailureRetryCheck::check( + const MatchFinder::MatchResult &Result) { + const auto &BinOp = *Result.Nodes.getNodeAs("binop"); + diag(BinOp.getOperatorLoc(), "top-level comparison in TEMP_FAILURE_RETRY"); + + // FIXME: FixIts would be nice, but potentially nontrivial when nested macros + // happen, e.g. `TEMP_FAILURE_RETRY(IS_ZERO(foo()))` +} + +} // namespace android +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/android/ComparisonInTempFailureRetryCheck.h b/clang-tidy/android/ComparisonInTempFailureRetryCheck.h new file mode 100644 index 000000000..de812323a --- /dev/null +++ b/clang-tidy/android/ComparisonInTempFailureRetryCheck.h @@ -0,0 +1,36 @@ +//===--- ComparisonInTempFailureRetryCheck.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_COMPARISONINTEMPFAILURERETRYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ANDROID_COMPARISONINTEMPFAILURERETRYCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace android { + +/// Attempts to catch calls to TEMP_FAILURE_RETRY with a top-level comparison +/// operation, like `TEMP_FAILURE_RETRY(read(...) != N)`. In these cases, the +/// comparison should go outside of the TEMP_FAILURE_RETRY. +/// +/// TEMP_FAILURE_RETRY is a macro provided by both glibc and Bionic. +class ComparisonInTempFailureRetryCheck : public ClangTidyCheck { +public: + ComparisonInTempFailureRetryCheck(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_COMPARISONINTEMPFAILURERETRYCHECK_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..1775440d5 --- /dev/null +++ b/clang-tidy/boost/UseToStringCheck.cpp @@ -0,0 +1,75 @@ +//===--- 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 { + +namespace { +AST_MATCHER(Type, isStrictlyInteger) { + return Node.isIntegerType() && !Node.isAnyCharacterType() && + !Node.isBooleanType(); +} +} // namespace + +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/ArgumentCommentCheck.cpp b/clang-tidy/bugprone/ArgumentCommentCheck.cpp new file mode 100644 index 000000000..a8da703ed --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/ArgumentCommentCheck.h b/clang-tidy/bugprone/ArgumentCommentCheck.h new file mode 100644 index 000000000..2f5a751b5 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_ARGUMENTCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_ARGUMENTCOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_ARGUMENTCOMMENTCHECK_H diff --git a/clang-tidy/bugprone/AssertSideEffectCheck.cpp b/clang-tidy/bugprone/AssertSideEffectCheck.cpp new file mode 100644 index 000000000..244e75521 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/AssertSideEffectCheck.h b/clang-tidy/bugprone/AssertSideEffectCheck.h new file mode 100644 index 000000000..0f386c9de --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_ASSERTSIDEEFFECTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_ASSERTSIDEEFFECTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_ASSERTSIDEEFFECTCHECK_H diff --git a/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp b/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.cpp new file mode 100644 index 000000000..ed2c2db95 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.h b/clang-tidy/bugprone/BoolPointerImplicitConversionCheck.h new file mode 100644 index 000000000..b3416a92b --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_BOOLPOINTERIMPLICITCONVERSIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BOOLPOINTERIMPLICITCONVERSIONCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BOOLPOINTERIMPLICITCONVERSIONCHECK_H diff --git a/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tidy/bugprone/BugproneTidyModule.cpp new file mode 100644 index 000000000..09a252b30 --- /dev/null +++ b/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -0,0 +1,155 @@ +//===--- 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 "../cppcoreguidelines/NarrowingConversionsCheck.h" +#include "ArgumentCommentCheck.h" +#include "AssertSideEffectCheck.h" +#include "BoolPointerImplicitConversionCheck.h" +#include "CopyConstructorInitCheck.h" +#include "DanglingHandleCheck.h" +#include "ExceptionEscapeCheck.h" +#include "FoldInitTypeCheck.h" +#include "ForwardDeclarationNamespaceCheck.h" +#include "ForwardingReferenceOverloadCheck.h" +#include "InaccurateEraseCheck.h" +#include "IncorrectRoundingsCheck.h" +#include "IntegerDivisionCheck.h" +#include "LambdaFunctionNameCheck.h" +#include "MacroParenthesesCheck.h" +#include "MacroRepeatedSideEffectsCheck.h" +#include "MisplacedOperatorInStrlenInAllocCheck.h" +#include "MisplacedWideningCastCheck.h" +#include "MoveForwardingReferenceCheck.h" +#include "MultipleStatementMacroCheck.h" +#include "ParentVirtualCallCheck.h" +#include "SizeofContainerCheck.h" +#include "SizeofExpressionCheck.h" +#include "StringConstructorCheck.h" +#include "StringIntegerAssignmentCheck.h" +#include "StringLiteralWithEmbeddedNulCheck.h" +#include "SuspiciousEnumUsageCheck.h" +#include "SuspiciousMemsetUsageCheck.h" +#include "SuspiciousMissingCommaCheck.h" +#include "SuspiciousSemicolonCheck.h" +#include "SuspiciousStringCompareCheck.h" +#include "SwappedArgumentsCheck.h" +#include "TerminatingContinueCheck.h" +#include "ThrowKeywordMissingCheck.h" +#include "UndefinedMemoryManipulationCheck.h" +#include "UndelegatedConstructorCheck.h" +#include "UnusedRaiiCheck.h" +#include "UnusedReturnValueCheck.h" +#include "UseAfterMoveCheck.h" +#include "VirtualNearMissCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +class BugproneModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "bugprone-argument-comment"); + CheckFactories.registerCheck( + "bugprone-assert-side-effect"); + CheckFactories.registerCheck( + "bugprone-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck( + "bugprone-copy-constructor-init"); + CheckFactories.registerCheck( + "bugprone-dangling-handle"); + CheckFactories.registerCheck( + "bugprone-exception-escape"); + CheckFactories.registerCheck( + "bugprone-fold-init-type"); + CheckFactories.registerCheck( + "bugprone-forward-declaration-namespace"); + CheckFactories.registerCheck( + "bugprone-forwarding-reference-overload"); + CheckFactories.registerCheck( + "bugprone-inaccurate-erase"); + CheckFactories.registerCheck( + "bugprone-incorrect-roundings"); + CheckFactories.registerCheck( + "bugprone-integer-division"); + CheckFactories.registerCheck( + "bugprone-lambda-function-name"); + CheckFactories.registerCheck( + "bugprone-macro-parentheses"); + CheckFactories.registerCheck( + "bugprone-macro-repeated-side-effects"); + CheckFactories.registerCheck( + "bugprone-misplaced-operator-in-strlen-in-alloc"); + CheckFactories.registerCheck( + "bugprone-misplaced-widening-cast"); + CheckFactories.registerCheck( + "bugprone-move-forwarding-reference"); + CheckFactories.registerCheck( + "bugprone-multiple-statement-macro"); + CheckFactories.registerCheck( + "bugprone-narrowing-conversions"); + CheckFactories.registerCheck( + "bugprone-parent-virtual-call"); + CheckFactories.registerCheck( + "bugprone-sizeof-container"); + CheckFactories.registerCheck( + "bugprone-sizeof-expression"); + CheckFactories.registerCheck( + "bugprone-string-constructor"); + CheckFactories.registerCheck( + "bugprone-string-integer-assignment"); + CheckFactories.registerCheck( + "bugprone-string-literal-with-embedded-nul"); + CheckFactories.registerCheck( + "bugprone-suspicious-enum-usage"); + CheckFactories.registerCheck( + "bugprone-suspicious-memset-usage"); + CheckFactories.registerCheck( + "bugprone-suspicious-missing-comma"); + CheckFactories.registerCheck( + "bugprone-suspicious-semicolon"); + CheckFactories.registerCheck( + "bugprone-suspicious-string-compare"); + CheckFactories.registerCheck( + "bugprone-swapped-arguments"); + CheckFactories.registerCheck( + "bugprone-terminating-continue"); + CheckFactories.registerCheck( + "bugprone-throw-keyword-missing"); + CheckFactories.registerCheck( + "bugprone-undefined-memory-manipulation"); + CheckFactories.registerCheck( + "bugprone-undelegated-constructor"); + CheckFactories.registerCheck( + "bugprone-unused-raii"); + CheckFactories.registerCheck( + "bugprone-unused-return-value"); + CheckFactories.registerCheck( + "bugprone-use-after-move"); + CheckFactories.registerCheck( + "bugprone-virtual-near-miss"); + } +}; + +} // 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..b20997cc4 --- /dev/null +++ b/clang-tidy/bugprone/CMakeLists.txt @@ -0,0 +1,55 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyBugproneModule + ArgumentCommentCheck.cpp + AssertSideEffectCheck.cpp + BoolPointerImplicitConversionCheck.cpp + BugproneTidyModule.cpp + CopyConstructorInitCheck.cpp + DanglingHandleCheck.cpp + ExceptionEscapeCheck.cpp + FoldInitTypeCheck.cpp + ForwardDeclarationNamespaceCheck.cpp + ForwardingReferenceOverloadCheck.cpp + InaccurateEraseCheck.cpp + IncorrectRoundingsCheck.cpp + IntegerDivisionCheck.cpp + LambdaFunctionNameCheck.cpp + MacroParenthesesCheck.cpp + MacroRepeatedSideEffectsCheck.cpp + MisplacedOperatorInStrlenInAllocCheck.cpp + MisplacedWideningCastCheck.cpp + MoveForwardingReferenceCheck.cpp + MultipleStatementMacroCheck.cpp + ParentVirtualCallCheck.cpp + SizeofContainerCheck.cpp + SizeofExpressionCheck.cpp + StringConstructorCheck.cpp + StringIntegerAssignmentCheck.cpp + StringLiteralWithEmbeddedNulCheck.cpp + SuspiciousEnumUsageCheck.cpp + SuspiciousMemsetUsageCheck.cpp + SuspiciousMissingCommaCheck.cpp + SuspiciousSemicolonCheck.cpp + SuspiciousStringCompareCheck.cpp + SwappedArgumentsCheck.cpp + TerminatingContinueCheck.cpp + ThrowKeywordMissingCheck.cpp + UndefinedMemoryManipulationCheck.cpp + UndelegatedConstructorCheck.cpp + UnusedRaiiCheck.cpp + UnusedReturnValueCheck.cpp + UseAfterMoveCheck.cpp + VirtualNearMissCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyCppCoreGuidelinesModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/bugprone/CopyConstructorInitCheck.cpp b/clang-tidy/bugprone/CopyConstructorInitCheck.cpp new file mode 100644 index 000000000..151e56c07 --- /dev/null +++ b/clang-tidy/bugprone/CopyConstructorInitCheck.cpp @@ -0,0 +1,121 @@ +//===--- CopyConstructorInitCheck.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 "CopyConstructorInitCheck.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 bugprone { + +void CopyConstructorInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // In the future this might be extended to move constructors? + Finder->addMatcher( + cxxConstructorDecl( + isCopyConstructor(), + hasAnyConstructorInitializer(cxxCtorInitializer( + isBaseInitializer(), + withInitializer(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(isDefaultConstructor())))))), + unless(isInstantiated())) + .bind("ctor"), + this); +} + +void CopyConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Ctor = Result.Nodes.getNodeAs("ctor"); + std::string ParamName = Ctor->getParamDecl(0)->getNameAsString(); + + // We want only one warning (and FixIt) for each ctor. + std::string FixItInitList; + bool HasRelevantBaseInit = false; + bool ShouldNotDoFixit = false; + bool HasWrittenInitializer = false; + SmallVector SafeFixIts; + for (const auto *Init : Ctor->inits()) { + bool CtorInitIsWritten = Init->isWritten(); + HasWrittenInitializer = HasWrittenInitializer || CtorInitIsWritten; + if (!Init->isBaseInitializer()) + continue; + const Type *BaseType = Init->getBaseClass(); + // Do not do fixits if there is a type alias involved or one of the bases + // are explicitly initialized. In the latter case we not do fixits to avoid + // -Wreorder warnings. + if (const auto *TempSpecTy = dyn_cast(BaseType)) + ShouldNotDoFixit = ShouldNotDoFixit || TempSpecTy->isTypeAlias(); + ShouldNotDoFixit = ShouldNotDoFixit || isa(BaseType); + ShouldNotDoFixit = ShouldNotDoFixit || CtorInitIsWritten; + const CXXRecordDecl *BaseClass = + BaseType->getAsCXXRecordDecl()->getDefinition(); + if (BaseClass->field_empty() && + BaseClass->forallBases( + [](const CXXRecordDecl *Class) { return Class->field_empty(); })) + continue; + bool NonCopyableBase = false; + for (const auto *Ctor : BaseClass->ctors()) { + if (Ctor->isCopyConstructor() && + (Ctor->getAccess() == AS_private || Ctor->isDeleted())) { + NonCopyableBase = true; + break; + } + } + if (NonCopyableBase) + continue; + const auto *CExpr = dyn_cast(Init->getInit()); + if (!CExpr || !CExpr->getConstructor()->isDefaultConstructor()) + continue; + HasRelevantBaseInit = true; + if (CtorInitIsWritten) { + if (!ParamName.empty()) + SafeFixIts.push_back( + FixItHint::CreateInsertion(CExpr->getLocEnd(), ParamName)); + } else { + if (Init->getSourceLocation().isMacroID() || + Ctor->getLocation().isMacroID() || ShouldNotDoFixit) + break; + FixItInitList += BaseClass->getNameAsString(); + FixItInitList += "(" + ParamName + "), "; + } + } + if (!HasRelevantBaseInit) + return; + + auto Diag = diag(Ctor->getLocation(), + "calling a base constructor other than the copy constructor") + << SafeFixIts; + + if (FixItInitList.empty() || ParamName.empty() || ShouldNotDoFixit) + return; + + std::string FixItMsg{FixItInitList.substr(0, FixItInitList.size() - 2)}; + SourceLocation FixItLoc; + // There is no initialization list in this constructor. + if (!HasWrittenInitializer) { + FixItLoc = Ctor->getBody()->getLocStart(); + FixItMsg = " : " + FixItMsg; + } else { + // We apply the missing ctors at the beginning of the initialization list. + FixItLoc = (*Ctor->init_begin())->getSourceLocation(); + FixItMsg += ','; + } + FixItMsg += ' '; + + Diag << FixItHint::CreateInsertion(FixItLoc, FixItMsg); +} // namespace misc + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/CopyConstructorInitCheck.h b/clang-tidy/bugprone/CopyConstructorInitCheck.h new file mode 100644 index 000000000..4d13da4a7 --- /dev/null +++ b/clang-tidy/bugprone/CopyConstructorInitCheck.h @@ -0,0 +1,36 @@ +//===--- CopyConstructorInitCheck.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_COPY_CONSTRUCTOR_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_COPY_CONSTRUCTOR_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds copy constructors where the ctor don't call the copy constructor of +/// the base class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-copy-constructor-init.html +class CopyConstructorInitCheck : public ClangTidyCheck { +public: + CopyConstructorInitCheck(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_BUGPRONE_COPY_CONSTRUCTOR_INIT_H diff --git a/clang-tidy/bugprone/DanglingHandleCheck.cpp b/clang-tidy/bugprone/DanglingHandleCheck.cpp new file mode 100644 index 000000000..a22dcad65 --- /dev/null +++ b/clang-tidy/bugprone/DanglingHandleCheck.cpp @@ -0,0 +1,188 @@ +//===--- 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 bugprone { + +namespace { + +ast_matchers::internal::BindableMatcher +handleFrom(const ast_matchers::internal::Matcher &IsAHandle, + const ast_matchers::internal::Matcher &Arg) { + return expr( + anyOf(cxxConstructExpr(hasDeclaration(cxxMethodDecl(ofClass(IsAHandle))), + hasArgument(0, Arg)), + cxxMemberCallExpr(hasType(cxxRecordDecl(IsAHandle)), + callee(memberExpr(member(cxxConversionDecl()))), + on(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(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(recordDecl(isASequence())))))))), + // For sequences and sets: insert. + cxxMemberCallExpr(callee(functionDecl(hasName("insert"))), + on(expr(hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl(IsAHandle))))), + hasInitializer( + exprWithCleanups(has(ignoringParenImpCasts(ConvertedHandle))) + .bind("bad_stmt"))), + this); + + // Find 'Handle foo = ReturnsAValue();' + Finder->addMatcher( + varDecl( + hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/DanglingHandleCheck.h b/clang-tidy/bugprone/DanglingHandleCheck.h new file mode 100644 index 000000000..add8d427d --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_DANGLING_HANDLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DANGLING_HANDLE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DANGLING_HANDLE_H diff --git a/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tidy/bugprone/ExceptionEscapeCheck.cpp new file mode 100644 index 000000000..416cf3fd8 --- /dev/null +++ b/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -0,0 +1,214 @@ +//===--- ExceptionEscapeCheck.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 "ExceptionEscapeCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/StringSet.h" + +using namespace clang::ast_matchers; + +namespace { +typedef llvm::SmallVector TypeVec; +} // namespace + +namespace clang { + +static bool isBaseOf(const Type *DerivedType, const Type *BaseType) { + const auto *DerivedClass = DerivedType->getAsCXXRecordDecl(); + const auto *BaseClass = BaseType->getAsCXXRecordDecl(); + if (!DerivedClass || !BaseClass) + return false; + + return !DerivedClass->forallBases( + [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; }); +} + +static const TypeVec +throwsException(const Stmt *St, const TypeVec &Caught, + llvm::SmallSet &CallStack); + +static const TypeVec +throwsException(const FunctionDecl *Func, + llvm::SmallSet &CallStack) { + if (CallStack.count(Func)) + return TypeVec(); + + if (const Stmt *Body = Func->getBody()) { + CallStack.insert(Func); + const TypeVec Result = throwsException(Body, TypeVec(), CallStack); + CallStack.erase(Func); + return Result; + } + + TypeVec Result; + if (const auto *FPT = Func->getType()->getAs()) { + for (const QualType Ex : FPT->exceptions()) { + Result.push_back(Ex.getTypePtr()); + } + } + return Result; +} + +static const TypeVec +throwsException(const Stmt *St, const TypeVec &Caught, + llvm::SmallSet &CallStack) { + TypeVec Results; + + if (!St) + return Results; + + if (const auto *Throw = dyn_cast(St)) { + if (const auto *ThrownExpr = Throw->getSubExpr()) { + const auto *ThrownType = + ThrownExpr->getType()->getUnqualifiedDesugaredType(); + if (ThrownType->isReferenceType()) { + ThrownType = ThrownType->castAs() + ->getPointeeType() + ->getUnqualifiedDesugaredType(); + } + if (const auto *TD = ThrownType->getAsTagDecl()) { + if (TD->getDeclName().isIdentifier() && TD->getName() == "bad_alloc" + && TD->isInStdNamespace()) + return Results; + } + Results.push_back(ThrownExpr->getType()->getUnqualifiedDesugaredType()); + } else { + Results.append(Caught.begin(), Caught.end()); + } + } else if (const auto *Try = dyn_cast(St)) { + TypeVec Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack); + for (unsigned i = 0; i < Try->getNumHandlers(); ++i) { + const CXXCatchStmt *Catch = Try->getHandler(i); + if (!Catch->getExceptionDecl()) { + const TypeVec Rethrown = + throwsException(Catch->getHandlerBlock(), Uncaught, CallStack); + Results.append(Rethrown.begin(), Rethrown.end()); + Uncaught.clear(); + } else { + const auto *CaughtType = + Catch->getCaughtType()->getUnqualifiedDesugaredType(); + if (CaughtType->isReferenceType()) { + CaughtType = CaughtType->castAs() + ->getPointeeType() + ->getUnqualifiedDesugaredType(); + } + auto NewEnd = + llvm::remove_if(Uncaught, [&CaughtType](const Type *ThrownType) { + return ThrownType == CaughtType || + isBaseOf(ThrownType, CaughtType); + }); + if (NewEnd != Uncaught.end()) { + Uncaught.erase(NewEnd, Uncaught.end()); + const TypeVec Rethrown = throwsException( + Catch->getHandlerBlock(), TypeVec(1, CaughtType), CallStack); + Results.append(Rethrown.begin(), Rethrown.end()); + } + } + } + Results.append(Uncaught.begin(), Uncaught.end()); + } else if (const auto *Call = dyn_cast(St)) { + if (const FunctionDecl *Func = Call->getDirectCallee()) { + TypeVec Excs = throwsException(Func, CallStack); + Results.append(Excs.begin(), Excs.end()); + } + } else { + for (const Stmt *Child : St->children()) { + TypeVec Excs = throwsException(Child, Caught, CallStack); + Results.append(Excs.begin(), Excs.end()); + } + } + return Results; +} + +static const TypeVec throwsException(const FunctionDecl *Func) { + llvm::SmallSet CallStack; + return throwsException(Func, CallStack); +} + +namespace ast_matchers { +AST_MATCHER_P(FunctionDecl, throws, internal::Matcher, InnerMatcher) { + TypeVec ExceptionList = throwsException(&Node); + auto NewEnd = llvm::remove_if( + ExceptionList, [this, Finder, Builder](const Type *Exception) { + return !InnerMatcher.matches(*Exception, Finder, Builder); + }); + ExceptionList.erase(NewEnd, ExceptionList.end()); + return ExceptionList.size(); +} + +AST_MATCHER_P(Type, isIgnored, llvm::StringSet<>, IgnoredExceptions) { + if (const auto *TD = Node.getAsTagDecl()) { + if (TD->getDeclName().isIdentifier()) + return IgnoredExceptions.count(TD->getName()) > 0; + } + return false; +} + +AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>, + FunctionsThatShouldNotThrow) { + return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0; +} +} // namespace ast_matchers + +namespace tidy { +namespace bugprone { + +ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get( + "FunctionsThatShouldNotThrow", "")), + RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) { + llvm::SmallVector FunctionsThatShouldNotThrowVec, + IgnoredExceptionsVec; + StringRef(RawFunctionsThatShouldNotThrow) + .split(FunctionsThatShouldNotThrowVec, ",", -1, false); + FunctionsThatShouldNotThrow.insert(FunctionsThatShouldNotThrowVec.begin(), + FunctionsThatShouldNotThrowVec.end()); + StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false); + IgnoredExceptions.insert(IgnoredExceptionsVec.begin(), + IgnoredExceptionsVec.end()); +} + +void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "FunctionsThatShouldNotThrow", + RawFunctionsThatShouldNotThrow); + Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions); +} + +void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl(allOf(anyOf(isNoThrow(), cxxDestructorDecl(), + cxxConstructorDecl(isMoveConstructor()), + cxxMethodDecl(isMoveAssignmentOperator()), + hasName("main"), hasName("swap"), + isEnabled(FunctionsThatShouldNotThrow)), + throws(unless(isIgnored(IgnoredExceptions))))) + .bind("thrower"), + this); +} + +void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { + const FunctionDecl *MatchedDecl = + Result.Nodes.getNodeAs("thrower"); + if (!MatchedDecl) + return; + + // FIXME: We should provide more information about the exact location where + // the exception is thrown, maybe the full path the exception escapes + diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 " + "which should not throw exceptions") << MatchedDecl; +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tidy/bugprone/ExceptionEscapeCheck.h new file mode 100644 index 000000000..d690022aa --- /dev/null +++ b/clang-tidy/bugprone/ExceptionEscapeCheck.h @@ -0,0 +1,47 @@ +//===--- ExceptionEscapeCheck.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_EXCEPTION_ESCAPE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/StringSet.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds functions which should not throw exceptions: Destructors, move +/// constructors, move assignment operators, the main() function, +/// swap() functions, functions marked with throw() or noexcept and functions +/// given as option to the checker. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-exception-escape.html +class ExceptionEscapeCheck : public ClangTidyCheck { +public: + ExceptionEscapeCheck(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: + std::string RawFunctionsThatShouldNotThrow; + std::string RawIgnoredExceptions; + + llvm::StringSet<> FunctionsThatShouldNotThrow; + llvm::StringSet<> IgnoredExceptions; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_EXCEPTION_ESCAPE_H diff --git a/clang-tidy/bugprone/FoldInitTypeCheck.cpp b/clang-tidy/bugprone/FoldInitTypeCheck.cpp new file mode 100644 index 000000000..6d7fd2852 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/FoldInitTypeCheck.h b/clang-tidy/bugprone/FoldInitTypeCheck.h new file mode 100644 index 000000000..e6170de0a --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_FOLD_INIT_TYPE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FOLD_INIT_TYPE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FOLD_INIT_TYPE_H diff --git a/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp b/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.cpp new file mode 100644 index 000000000..9ea5b553d --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.h b/clang-tidy/bugprone/ForwardDeclarationNamespaceCheck.h new file mode 100644 index 000000000..c3d301868 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_FORWARDDECLARATIONNAMESPACECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FORWARDDECLARATIONNAMESPACECHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallPtrSet.h" +#include +#include + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FORWARDDECLARATIONNAMESPACECHECK_H diff --git a/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp b/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.cpp new file mode 100644 index 000000000..17bdc76c9 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.h b/clang-tidy/bugprone/ForwardingReferenceOverloadCheck.h new file mode 100644 index 000000000..4b00ab293 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_FORWARDINGREFERENCEOVERLOADCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FORWARDINGREFERENCEOVERLOADCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_FORWARDINGREFERENCEOVERLOADCHECK_H diff --git a/clang-tidy/bugprone/InaccurateEraseCheck.cpp b/clang-tidy/bugprone/InaccurateEraseCheck.cpp new file mode 100644 index 000000000..cf1be0e7b --- /dev/null +++ b/clang-tidy/bugprone/InaccurateEraseCheck.cpp @@ -0,0 +1,81 @@ +//===--- 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 bugprone { + +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 = type(hasUnqualifiedDesugaredType( + tagType(hasDeclaration(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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/InaccurateEraseCheck.h b/clang-tidy/bugprone/InaccurateEraseCheck.h new file mode 100644 index 000000000..d6b372908 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_INACCURATEERASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INACCURATEERASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INACCURATEERASECHECK_H diff --git a/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp b/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp new file mode 100644 index 000000000..ab7b28d69 --- /dev/null +++ b/clang-tidy/bugprone/IncorrectRoundingsCheck.cpp @@ -0,0 +1,71 @@ +//===--- IncorrectRoundingsCheck.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 "IncorrectRoundingsCheck.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 bugprone { + +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 IncorrectRoundingsCheck::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 IncorrectRoundingsCheck::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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/IncorrectRoundingsCheck.h b/clang-tidy/bugprone/IncorrectRoundingsCheck.h new file mode 100644 index 000000000..b1886fd8a --- /dev/null +++ b/clang-tidy/bugprone/IncorrectRoundingsCheck.h @@ -0,0 +1,39 @@ +//===--- IncorrectRoundingsCheck.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_INCORRECTROUNDINGSCHECK_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INCORRECTROUNDINGSCHECK_H_ + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// \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 IncorrectRoundingsCheck : public ClangTidyCheck { +public: + IncorrectRoundingsCheck(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_INCORRECTROUNDINGSCHECK_H_ diff --git a/clang-tidy/bugprone/IntegerDivisionCheck.cpp b/clang-tidy/bugprone/IntegerDivisionCheck.cpp new file mode 100644 index 000000000..1b4eaeabb --- /dev/null +++ b/clang-tidy/bugprone/IntegerDivisionCheck.cpp @@ -0,0 +1,57 @@ +//===--- IntegerDivisionCheck.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 "IntegerDivisionCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void IntegerDivisionCheck::registerMatchers(MatchFinder *Finder) { + const auto IntType = hasType(isInteger()); + + const auto BinaryOperators = binaryOperator(anyOf( + hasOperatorName("%"), hasOperatorName("<<"), hasOperatorName(">>"), + hasOperatorName("<<"), hasOperatorName("^"), hasOperatorName("|"), + hasOperatorName("&"), hasOperatorName("||"), hasOperatorName("&&"), + hasOperatorName("<"), hasOperatorName(">"), hasOperatorName("<="), + hasOperatorName(">="), hasOperatorName("=="), hasOperatorName("!="))); + + const auto UnaryOperators = + unaryOperator(anyOf(hasOperatorName("~"), hasOperatorName("!"))); + + const auto Exceptions = + anyOf(BinaryOperators, conditionalOperator(), binaryConditionalOperator(), + callExpr(IntType), explicitCastExpr(IntType), UnaryOperators); + + Finder->addMatcher( + binaryOperator( + hasOperatorName("/"), hasLHS(expr(IntType)), hasRHS(expr(IntType)), + hasAncestor( + castExpr(hasCastKind(CK_IntegralToFloating)).bind("FloatCast")), + unless(hasAncestor( + expr(Exceptions, + hasAncestor(castExpr(equalsBoundNode("FloatCast"))))))) + .bind("IntDiv"), + this); +} + +void IntegerDivisionCheck::check(const MatchFinder::MatchResult &Result) { + const auto *IntDiv = Result.Nodes.getNodeAs("IntDiv"); + diag(IntDiv->getLocStart(), "result of integer division used in a floating " + "point context; possible loss of precision"); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/IntegerDivisionCheck.h b/clang-tidy/bugprone/IntegerDivisionCheck.h new file mode 100644 index 000000000..307e49384 --- /dev/null +++ b/clang-tidy/bugprone/IntegerDivisionCheck.h @@ -0,0 +1,36 @@ +//===--- IntegerDivisionCheck.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_INTEGER_DIVISION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_INTEGER_DIVISION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds cases where integer division in a floating point context is likely to +/// cause unintended loss of precision. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-integer-division.html +class IntegerDivisionCheck : public ClangTidyCheck { +public: + IntegerDivisionCheck(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_INTEGER_DIVISION_H diff --git a/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp b/clang-tidy/bugprone/LambdaFunctionNameCheck.cpp new file mode 100644 index 000000000..55dbe8bf3 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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(ER.getAsRange()) != + 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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/LambdaFunctionNameCheck.h b/clang-tidy/bugprone/LambdaFunctionNameCheck.h new file mode 100644 index 000000000..b7b1b4417 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_LAMBDAFUNCTIONNAMECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_LAMBDAFUNCTIONNAMECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_LAMBDAFUNCTIONNAMECHECK_H diff --git a/clang-tidy/bugprone/MacroParenthesesCheck.cpp b/clang-tidy/bugprone/MacroParenthesesCheck.cpp new file mode 100644 index 000000000..6846bc2f7 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/MacroParenthesesCheck.h b/clang-tidy/bugprone/MacroParenthesesCheck.h new file mode 100644 index 000000000..383a6cceb --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_MACROPARENTHESESCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROPARENTHESESCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROPARENTHESESCHECK_H diff --git a/clang-tidy/bugprone/MacroRepeatedSideEffectsCheck.cpp b/clang-tidy/bugprone/MacroRepeatedSideEffectsCheck.cpp new file mode 100644 index 000000000..30c770e34 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/MacroRepeatedSideEffectsCheck.h b/clang-tidy/bugprone/MacroRepeatedSideEffectsCheck.h new file mode 100644 index 000000000..a2a313414 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_MACROREPEATEDSIDEEFFECTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROREPEATEDSIDEEFFECTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MACROREPEATEDSIDEEFFECTSCHECK_H diff --git a/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp b/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp new file mode 100644 index 000000000..f8db0b3ef --- /dev/null +++ b/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.cpp @@ -0,0 +1,112 @@ +//===--- MisplacedOperatorInStrlenInAllocCheck.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 "MisplacedOperatorInStrlenInAllocCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void MisplacedOperatorInStrlenInAllocCheck::registerMatchers( + MatchFinder *Finder) { + const auto StrLenFunc = functionDecl(anyOf( + hasName("::strlen"), hasName("::std::strlen"), hasName("::strnlen"), + hasName("::std::strnlen"), hasName("::strnlen_s"), + hasName("::std::strnlen_s"), hasName("::wcslen"), + hasName("::std::wcslen"), hasName("::wcsnlen"), hasName("::std::wcsnlen"), + hasName("::wcsnlen_s"), hasName("std::wcsnlen_s"))); + + const auto BadUse = + callExpr(callee(StrLenFunc), + hasAnyArgument(ignoringImpCasts( + binaryOperator(allOf(hasOperatorName("+"), + hasRHS(ignoringParenImpCasts( + integerLiteral(equals(1)))))) + .bind("BinOp")))) + .bind("StrLen"); + + const auto BadArg = anyOf( + allOf(hasDescendant(BadUse), + unless(binaryOperator(allOf( + hasOperatorName("+"), hasLHS(BadUse), + hasRHS(ignoringParenImpCasts(integerLiteral(equals(1)))))))), + BadUse); + + const auto Alloc0Func = + functionDecl(anyOf(hasName("::malloc"), hasName("std::malloc"), + hasName("::alloca"), hasName("std::alloca"))); + const auto Alloc1Func = + functionDecl(anyOf(hasName("::calloc"), hasName("std::calloc"), + hasName("::realloc"), hasName("std::realloc"))); + + const auto Alloc0FuncPtr = + varDecl(hasType(isConstQualified()), + hasInitializer(ignoringParenImpCasts( + declRefExpr(hasDeclaration(Alloc0Func))))); + const auto Alloc1FuncPtr = + varDecl(hasType(isConstQualified()), + hasInitializer(ignoringParenImpCasts( + declRefExpr(hasDeclaration(Alloc1Func))))); + + Finder->addMatcher(callExpr(callee(decl(anyOf(Alloc0Func, Alloc0FuncPtr))), + hasArgument(0, BadArg)) + .bind("Alloc"), + this); + Finder->addMatcher(callExpr(callee(decl(anyOf(Alloc1Func, Alloc1FuncPtr))), + hasArgument(1, BadArg)) + .bind("Alloc"), + this); + Finder->addMatcher( + cxxNewExpr(isArray(), hasArraySize(BadArg)).bind("Alloc"), this); +} + +void MisplacedOperatorInStrlenInAllocCheck::check( + const MatchFinder::MatchResult &Result) { + const Expr *Alloc = Result.Nodes.getNodeAs("Alloc"); + if (!Alloc) + Alloc = Result.Nodes.getNodeAs("Alloc"); + assert(Alloc && "Matched node bound by 'Alloc' shoud be either 'CallExpr'" + " or 'CXXNewExpr'"); + + const auto *StrLen = Result.Nodes.getNodeAs("StrLen"); + const auto *BinOp = Result.Nodes.getNodeAs("BinOp"); + + const StringRef StrLenText = Lexer::getSourceText( + CharSourceRange::getTokenRange(StrLen->getSourceRange()), + *Result.SourceManager, getLangOpts()); + const StringRef Arg0Text = Lexer::getSourceText( + CharSourceRange::getTokenRange(StrLen->getArg(0)->getSourceRange()), + *Result.SourceManager, getLangOpts()); + const StringRef StrLenBegin = StrLenText.substr(0, StrLenText.find(Arg0Text)); + const StringRef StrLenEnd = StrLenText.substr( + StrLenText.find(Arg0Text) + Arg0Text.size(), StrLenText.size()); + + const StringRef LHSText = Lexer::getSourceText( + CharSourceRange::getTokenRange(BinOp->getLHS()->getSourceRange()), + *Result.SourceManager, getLangOpts()); + const StringRef RHSText = Lexer::getSourceText( + CharSourceRange::getTokenRange(BinOp->getRHS()->getSourceRange()), + *Result.SourceManager, getLangOpts()); + + auto Hint = FixItHint::CreateReplacement( + StrLen->getSourceRange(), + (StrLenBegin + LHSText + StrLenEnd + " + " + RHSText).str()); + + diag(Alloc->getLocStart(), + "addition operator is applied to the argument of %0 instead of its " + "result") << StrLen->getDirectCallee()->getName() << Hint; +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h b/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h new file mode 100644 index 000000000..99cfcfbbf --- /dev/null +++ b/clang-tidy/bugprone/MisplacedOperatorInStrlenInAllocCheck.h @@ -0,0 +1,37 @@ +//===--- MisplacedOperatorInStrlenInAllocCheck.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_MISPLACED_OPERATOR_IN_STRLEN_IN_ALLOC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MISPLACED_OPERATOR_IN_STRLEN_IN_ALLOC_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds cases where ``1`` is added to the string in the argument to a function +/// in the ``strlen()`` family instead of the result and value is used as an +/// argument to a memory allocation function. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-misplaced-operator-in-strlen-in-alloc.html +class MisplacedOperatorInStrlenInAllocCheck : public ClangTidyCheck { +public: + MisplacedOperatorInStrlenInAllocCheck(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_MISPLACED_OPERATOR_IN_STRLEN_IN_ALLOC_H diff --git a/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp b/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp new file mode 100644 index 000000000..e263366b6 --- /dev/null +++ b/clang-tidy/bugprone/MisplacedWideningCastCheck.cpp @@ -0,0 +1,233 @@ +//===--- 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 bugprone { + +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; + + if (Cast->isTypeDependent() || Cast->isValueDependent() || + Calc->isTypeDependent() || Calc->isValueDependent()) + 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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/MisplacedWideningCastCheck.h b/clang-tidy/bugprone/MisplacedWideningCastCheck.h new file mode 100644 index 000000000..b61556fdb --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_MISPLACEDWIDENINGCASTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MISPLACEDWIDENINGCASTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif diff --git a/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp b/clang-tidy/bugprone/MoveForwardingReferenceCheck.cpp new file mode 100644 index 000000000..516ee1930 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/MoveForwardingReferenceCheck.h b/clang-tidy/bugprone/MoveForwardingReferenceCheck.h new file mode 100644 index 000000000..c61de757d --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_MOVEFORWARDINGREFERENCECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MOVEFORWARDINGREFERENCECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MOVEFORWARDINGREFERENCECHECK_H diff --git a/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp b/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp new file mode 100644 index 000000000..942c8752f --- /dev/null +++ b/clang-tidy/bugprone/MultipleStatementMacroCheck.cpp @@ -0,0 +1,107 @@ +//===--- 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 bugprone { + +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).getAsRange()); + Loc = Locs.back().getBegin(); + } + 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().getBegin(), "multiple statement macro used without " + "braces; some statements will be " + "unconditionally executed"); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/MultipleStatementMacroCheck.h b/clang-tidy/bugprone/MultipleStatementMacroCheck.h new file mode 100644 index 000000000..efc659910 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_MULTIPLE_STATEMENT_MACRO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MULTIPLE_STATEMENT_MACRO_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MULTIPLE_STATEMENT_MACRO_H diff --git a/clang-tidy/bugprone/ParentVirtualCallCheck.cpp b/clang-tidy/bugprone/ParentVirtualCallCheck.cpp new file mode 100755 index 000000000..919a6910f --- /dev/null +++ b/clang-tidy/bugprone/ParentVirtualCallCheck.cpp @@ -0,0 +1,156 @@ +//===--- ParentVirtualCallCheck.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 "ParentVirtualCallCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +using BasesVector = llvm::SmallVector; + +static bool isParentOf(const CXXRecordDecl &Parent, + const CXXRecordDecl &ThisClass) { + if (Parent.getCanonicalDecl() == ThisClass.getCanonicalDecl()) + return true; + const CXXRecordDecl *ParentCanonicalDecl = Parent.getCanonicalDecl(); + return ThisClass.bases_end() != + llvm::find_if(ThisClass.bases(), [=](const CXXBaseSpecifier &Base) { + auto *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + assert(BaseDecl); + return ParentCanonicalDecl == BaseDecl->getCanonicalDecl(); + }); +} + +static BasesVector getParentsByGrandParent(const CXXRecordDecl &GrandParent, + const CXXRecordDecl &ThisClass, + const CXXMethodDecl &MemberDecl) { + BasesVector Result; + for (const auto &Base : ThisClass.bases()) { + const auto *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + const CXXMethodDecl *ActualMemberDecl = + MemberDecl.getCorrespondingMethodInClass(BaseDecl); + if (!ActualMemberDecl) + continue; + // TypePtr is the nearest base class to ThisClass between ThisClass and + // GrandParent, where MemberDecl is overridden. TypePtr is the class the + // check proposes to fix to. + const Type *TypePtr = + ActualMemberDecl->getThisType(ActualMemberDecl->getASTContext()) + .getTypePtr(); + const CXXRecordDecl *RecordDeclType = TypePtr->getPointeeCXXRecordDecl(); + assert(RecordDeclType && "TypePtr is not a pointer to CXXRecordDecl!"); + if (RecordDeclType->getCanonicalDecl()->isDerivedFrom(&GrandParent)) + Result.emplace_back(RecordDeclType); + } + + return Result; +} + +static std::string getNameAsString(const NamedDecl *Decl) { + std::string QualName; + llvm::raw_string_ostream OS(QualName); + PrintingPolicy PP(Decl->getASTContext().getPrintingPolicy()); + PP.SuppressUnwrittenScope = true; + Decl->printQualifiedName(OS, PP); + return OS.str(); +} + +// Returns E as written in the source code. Used to handle 'using' and +// 'typedef'ed names of grand-parent classes. +static std::string getExprAsString(const clang::Expr &E, + clang::ASTContext &AC) { + std::string Text = tooling::fixit::getText(E, AC).str(); + Text.erase( + llvm::remove_if( + Text, + [](char C) { return std::isspace(static_cast(C)); }), + Text.end()); + return Text; +} + +void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxMemberCallExpr( + callee(memberExpr(hasDescendant(implicitCastExpr( + hasImplicitDestinationType(pointsTo( + type(anything()).bind("castToType"))), + hasSourceExpression(cxxThisExpr(hasType( + type(anything()).bind("thisType"))))))) + .bind("member")), + callee(cxxMethodDecl(isVirtual()))), + this); +} + +void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Member = Result.Nodes.getNodeAs("member"); + assert(Member); + + if (!Member->getQualifier()) + return; + + const auto *MemberDecl = cast(Member->getMemberDecl()); + + const auto *ThisTypePtr = Result.Nodes.getNodeAs("thisType"); + assert(ThisTypePtr); + + const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl(); + assert(ThisType); + + const auto *CastToTypePtr = Result.Nodes.getNodeAs("castToType"); + assert(CastToTypePtr); + + const auto *CastToType = CastToTypePtr->getAsCXXRecordDecl(); + assert(CastToType); + + if (isParentOf(*CastToType, *ThisType)) + return; + + const BasesVector Parents = + getParentsByGrandParent(*CastToType, *ThisType, *MemberDecl); + + if (Parents.empty()) + return; + + std::string ParentsStr; + ParentsStr.reserve(30 * Parents.size()); + for (const CXXRecordDecl *Parent : Parents) { + if (!ParentsStr.empty()) + ParentsStr.append(" or "); + ParentsStr.append("'").append(getNameAsString(Parent)).append("'"); + } + + assert(Member->getQualifierLoc().getSourceRange().getBegin().isValid()); + auto Diag = diag(Member->getQualifierLoc().getSourceRange().getBegin(), + "qualified name '%0' refers to a member overridden " + "in subclass%1; did you mean %2?") + << getExprAsString(*Member, *Result.Context) + << (Parents.size() > 1 ? "es" : "") << ParentsStr; + + // Propose a fix if there's only one parent class... + if (Parents.size() == 1 && + // ...unless parent class is templated + !isa(Parents.front())) + Diag << FixItHint::CreateReplacement( + Member->getQualifierLoc().getSourceRange(), + getNameAsString(Parents.front()) + "::"); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/ParentVirtualCallCheck.h b/clang-tidy/bugprone/ParentVirtualCallCheck.h new file mode 100755 index 000000000..08c3aefb0 --- /dev/null +++ b/clang-tidy/bugprone/ParentVirtualCallCheck.h @@ -0,0 +1,35 @@ +//===--- ParentVirtualCallCheck.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_PARENTVIRTUALCALLCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_PARENTVIRTUALCALLCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds calls to grand..-parent virtual methods instead of parent's. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-parent-virtual-call.html +class ParentVirtualCallCheck : public ClangTidyCheck { +public: + ParentVirtualCallCheck(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_PARENTVIRTUALCALLCHECK_H diff --git a/clang-tidy/bugprone/SizeofContainerCheck.cpp b/clang-tidy/bugprone/SizeofContainerCheck.cpp new file mode 100644 index 000000000..b4a019e99 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SizeofContainerCheck.h b/clang-tidy/bugprone/SizeofContainerCheck.h new file mode 100644 index 000000000..76b82b007 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_SIZEOFCONTAINERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFCONTAINERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFCONTAINERCHECK_H diff --git a/clang-tidy/bugprone/SizeofExpressionCheck.cpp b/clang-tidy/bugprone/SizeofExpressionCheck.cpp new file mode 100644 index 000000000..f05a90064 --- /dev/null +++ b/clang-tidy/bugprone/SizeofExpressionCheck.cpp @@ -0,0 +1,284 @@ +//===--- 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 bugprone { + +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), + WarnOnSizeOfIntegerExpression( + Options.get("WarnOnSizeOfIntegerExpression", 0) != 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, "WarnOnSizeOfIntegerExpression", + WarnOnSizeOfIntegerExpression); + 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 IntegerCallExpr = expr(ignoringParenImpCasts( + callExpr(anyOf(hasType(isInteger()), hasType(enumType())), + unless(isInTemplateInstantiation())))); + 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 sizeof(f()) + if (WarnOnSizeOfIntegerExpression) { + Finder->addMatcher( + expr(sizeOfExpr(ignoringParenImpCasts(has(IntegerCallExpr)))) + .bind("sizeof-integer-call"), + 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-integer-call")) { + diag(E->getLocStart(), "suspicious usage of 'sizeof()' on an expression " + "that results in an integer"); + } 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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SizeofExpressionCheck.h b/clang-tidy/bugprone/SizeofExpressionCheck.h new file mode 100644 index 000000000..8e14c3154 --- /dev/null +++ b/clang-tidy/bugprone/SizeofExpressionCheck.h @@ -0,0 +1,41 @@ +//===--- 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_BUGPRONE_SIZEOFEXPRESSIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFEXPRESSIONCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Find suspicious usages of sizeof expression. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 WarnOnSizeOfIntegerExpression; + const bool WarnOnSizeOfThis; + const bool WarnOnSizeOfCompareToConstant; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SIZEOFEXPRESSIONCHECK_H diff --git a/clang-tidy/bugprone/StringConstructorCheck.cpp b/clang-tidy/bugprone/StringConstructorCheck.cpp new file mode 100644 index 000000000..420428fc3 --- /dev/null +++ b/clang-tidy/bugprone/StringConstructorCheck.cpp @@ -0,0 +1,136 @@ +//===--- 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 bugprone { + +namespace { +AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) { + return Node.getValue().getZExtValue() > N; +} +} // namespace + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/StringConstructorCheck.h b/clang-tidy/bugprone/StringConstructorCheck.h new file mode 100644 index 000000000..52e9791fd --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_STRING_CONSTRUCTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_STRING_CONSTRUCTOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds suspicious string constructor and check their parameters. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_STRING_CONSTRUCTOR_H diff --git a/clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp b/clang-tidy/bugprone/StringIntegerAssignmentCheck.cpp new file mode 100644 index 000000000..f3736489c --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/StringIntegerAssignmentCheck.h b/clang-tidy/bugprone/StringIntegerAssignmentCheck.h new file mode 100644 index 000000000..42fa53e96 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_STRINGINTEGERASSIGNMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_STRINGINTEGERASSIGNMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_STRINGINTEGERASSIGNMENTCHECK_H diff --git a/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp b/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp new file mode 100644 index 000000000..eaa610fc6 --- /dev/null +++ b/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.cpp @@ -0,0 +1,85 @@ +//===--- 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 bugprone { + +namespace { +AST_MATCHER(StringLiteral, containsNul) { + for (size_t i = 0; i < Node.getLength(); ++i) + if (Node.getCodeUnit(i) == '\0') + return true; + return false; +} +} // namespace + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.h b/clang-tidy/bugprone/StringLiteralWithEmbeddedNulCheck.h new file mode 100644 index 000000000..f5341c314 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_STRINGLITERALWITHEMBEDDEDNULCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_STRINGLITERALWITHEMBEDDEDNULCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Find suspicious string literals with embedded NUL characters. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_STRINGLITERALWITHEMBEDDEDNULCHECK_H diff --git a/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp b/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp new file mode 100644 index 000000000..1abad4eb1 --- /dev/null +++ b/clang-tidy/bugprone/SuspiciousEnumUsageCheck.cpp @@ -0,0 +1,219 @@ +//===--- 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 bugprone { + +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 llvm::APSInt::compareValues(E1->getInitVal(), + E2->getInitVal()) < 0; + }); + 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 llvm::APSInt::compareValues(Range1.MaxVal, Range2.MinVal) < 0 || + llvm::APSInt::compareValues(Range2.MaxVal, Range1.MinVal) < 0; +} + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h b/clang-tidy/bugprone/SuspiciousEnumUsageCheck.h new file mode 100644 index 000000000..9c1b53d78 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_SUSPICIOUSENUMUSAGECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSENUMUSAGECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSENUMUSAGECHECK_H 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/SuspiciousMissingCommaCheck.cpp b/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp new file mode 100644 index 000000000..3831dc3ea --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SuspiciousMissingCommaCheck.h b/clang-tidy/bugprone/SuspiciousMissingCommaCheck.h new file mode 100644 index 000000000..4ae3ecf06 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_SUSPICIOUSMISSINGCOMMACHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSMISSINGCOMMACHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// This check finds string literals which are probably concatenated +/// accidentally. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSMISSINGCOMMACHECK_H diff --git a/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp b/clang-tidy/bugprone/SuspiciousSemicolonCheck.cpp new file mode 100644 index 000000000..d7f51d0d8 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SuspiciousSemicolonCheck.h b/clang-tidy/bugprone/SuspiciousSemicolonCheck.h new file mode 100644 index 000000000..adfced1a1 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_SUSPICIOUSSEMICOLONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSSEMICOLONCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSSEMICOLONCHECK_H diff --git a/clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp b/clang-tidy/bugprone/SuspiciousStringCompareCheck.cpp new file mode 100644 index 000000000..0cc62157b --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +// 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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SuspiciousStringCompareCheck.h b/clang-tidy/bugprone/SuspiciousStringCompareCheck.h new file mode 100644 index 000000000..38a9d9cdd --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_SUSPICIOUSSTRINGCOMPARECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSSTRINGCOMPARECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Find suspicious calls to string compare functions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSSTRINGCOMPARECHECK_H diff --git a/clang-tidy/bugprone/SwappedArgumentsCheck.cpp b/clang-tidy/bugprone/SwappedArgumentsCheck.cpp new file mode 100644 index 000000000..64d3eaf50 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/SwappedArgumentsCheck.h b/clang-tidy/bugprone/SwappedArgumentsCheck.h new file mode 100644 index 000000000..59ec3add3 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_SWAPPEDARGUMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SWAPPEDARGUMENTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SWAPPEDARGUMENTSCHECK_H diff --git a/clang-tidy/bugprone/TerminatingContinueCheck.cpp b/clang-tidy/bugprone/TerminatingContinueCheck.cpp new file mode 100644 index 000000000..3e11e06cf --- /dev/null +++ b/clang-tidy/bugprone/TerminatingContinueCheck.cpp @@ -0,0 +1,49 @@ +//===--- TerminatingContinueCheck.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 "TerminatingContinueCheck.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 bugprone { + +void TerminatingContinueCheck::registerMatchers(MatchFinder *Finder) { + const auto doWithFalse = + doStmt(hasCondition(ignoringImpCasts( + anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)), + cxxNullPtrLiteralExpr(), gnuNullExpr()))), + equalsBoundNode("closestLoop")); + + Finder->addMatcher( + continueStmt(hasAncestor(stmt(anyOf(forStmt(), whileStmt(), + cxxForRangeStmt(), doStmt())) + .bind("closestLoop")), + hasAncestor(doWithFalse)) + .bind("continue"), + this); +} + +void TerminatingContinueCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ContStmt = Result.Nodes.getNodeAs("continue"); + + auto Diag = + diag(ContStmt->getLocStart(), + "'continue' in loop with false condition is equivalent to 'break'") + << tooling::fixit::createReplacement(*ContStmt, "break"); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/TerminatingContinueCheck.h b/clang-tidy/bugprone/TerminatingContinueCheck.h new file mode 100644 index 000000000..5985693cd --- /dev/null +++ b/clang-tidy/bugprone/TerminatingContinueCheck.h @@ -0,0 +1,36 @@ +//===--- TerminatingContinueCheck.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_TERMINATINGCONTINUECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TERMINATINGCONTINUECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Checks if a 'continue' statement terminates the loop (i.e. the loop has +/// a condition which always evaluates to false). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-terminating-continue.html +class TerminatingContinueCheck : public ClangTidyCheck { +public: + TerminatingContinueCheck(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_TERMINATINGCONTINUECHECK_H diff --git a/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp b/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp new file mode 100644 index 000000000..350cf3bc6 --- /dev/null +++ b/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp @@ -0,0 +1,52 @@ +//===--- ThrowKeywordMissingCheck.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 "ThrowKeywordMissingCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +void ThrowKeywordMissingCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto CtorInitializerList = + cxxConstructorDecl(hasAnyConstructorInitializer(anything())); + + Finder->addMatcher( + expr(anyOf(cxxFunctionalCastExpr(), cxxBindTemporaryExpr(), + cxxTemporaryObjectExpr()), + hasType(cxxRecordDecl( + isSameOrDerivedFrom(matchesName("[Ee]xception|EXCEPTION")))), + unless(anyOf(hasAncestor(stmt( + anyOf(cxxThrowExpr(), callExpr(), returnStmt()))), + hasAncestor(varDecl()), + allOf(hasAncestor(CtorInitializerList), + unless(hasAncestor(cxxCatchStmt())))))) + .bind("temporary-exception-not-thrown"), + this); +} + +void ThrowKeywordMissingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *TemporaryExpr = + Result.Nodes.getNodeAs("temporary-exception-not-thrown"); + + diag(TemporaryExpr->getLocStart(), "suspicious exception object created but " + "not thrown; did you mean 'throw %0'?") + << TemporaryExpr->getType().getBaseTypeIdentifier()->getName(); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/ThrowKeywordMissingCheck.h b/clang-tidy/bugprone/ThrowKeywordMissingCheck.h new file mode 100644 index 000000000..cc57083c4 --- /dev/null +++ b/clang-tidy/bugprone/ThrowKeywordMissingCheck.h @@ -0,0 +1,36 @@ +//===--- ThrowKeywordMissingCheck.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_THROWKEYWORDMISSINGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_THROWKEYWORDMISSINGCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Emits a warning about temporary objects whose type is (or is derived from) a +/// class that has 'EXCEPTION', 'Exception' or 'exception' in its name. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-throw-keyword-missing.html +class ThrowKeywordMissingCheck : public ClangTidyCheck { +public: + ThrowKeywordMissingCheck(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_THROWKEYWORDMISSINGCHECK_H diff --git a/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp new file mode 100644 index 000000000..ebfe517dd --- /dev/null +++ b/clang-tidy/bugprone/UndefinedMemoryManipulationCheck.cpp @@ -0,0 +1,71 @@ +//===--- 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) { + // For incomplete types, assume they are TriviallyCopyable. + return Node.hasDefinition() ? !Node.isTriviallyCopyable() : false; +} +} // namespace + +void UndefinedMemoryManipulationCheck::registerMatchers(MatchFinder *Finder) { + const auto NotTriviallyCopyableObject = + hasType(ast_matchers::hasCanonicalType( + 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 *Call = Result.Nodes.getNodeAs("dest")) { + QualType DestType = Call->getArg(0)->IgnoreImplicit()->getType(); + if (!DestType->getPointeeType().isNull()) + DestType = DestType->getPointeeType(); + diag(Call->getLocStart(), "undefined behavior, destination object type %0 " + "is not TriviallyCopyable") + << DestType; + } + if (const auto *Call = Result.Nodes.getNodeAs("src")) { + QualType SourceType = Call->getArg(1)->IgnoreImplicit()->getType(); + if (!SourceType->getPointeeType().isNull()) + SourceType = SourceType->getPointeeType(); + diag(Call->getLocStart(), + "undefined behavior, source object type %0 is not TriviallyCopyable") + << SourceType; + } +} + +} // 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/bugprone/UndelegatedConstructorCheck.cpp b/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp new file mode 100644 index 000000000..f47ad7e7c --- /dev/null +++ b/clang-tidy/bugprone/UndelegatedConstructorCheck.cpp @@ -0,0 +1,84 @@ +//===--- UndelegatedConstructorCheck.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 "UndelegatedConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/UndelegatedConstructorCheck.h b/clang-tidy/bugprone/UndelegatedConstructorCheck.h new file mode 100644 index 000000000..ed881e153 --- /dev/null +++ b/clang-tidy/bugprone/UndelegatedConstructorCheck.h @@ -0,0 +1,36 @@ +//===--- UndelegatedConstructorCheck.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 bugprone { + +/// 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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H diff --git a/clang-tidy/bugprone/UnusedRaiiCheck.cpp b/clang-tidy/bugprone/UnusedRaiiCheck.cpp new file mode 100644 index 000000000..e2882f371 --- /dev/null +++ b/clang-tidy/bugprone/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 bugprone { + +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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/UnusedRaiiCheck.h b/clang-tidy/bugprone/UnusedRaiiCheck.h new file mode 100644 index 000000000..34190ece9 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_UNUSEDRAIICHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRAIICHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Finds temporaries that look like RAII objects. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRAIICHECK_H diff --git a/clang-tidy/bugprone/UnusedReturnValueCheck.cpp b/clang-tidy/bugprone/UnusedReturnValueCheck.cpp new file mode 100644 index 000000000..d0c588570 --- /dev/null +++ b/clang-tidy/bugprone/UnusedReturnValueCheck.cpp @@ -0,0 +1,100 @@ +//===--- UnusedReturnValueCheck.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 "UnusedReturnValueCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace bugprone { + +namespace { + +// Matches functions that are instantiated from a class template member function +// matching InnerMatcher. Functions not instantiated from a class template +// member function are matched directly with InnerMatcher. +AST_MATCHER_P(FunctionDecl, isInstantiatedFrom, Matcher, + InnerMatcher) { + FunctionDecl *InstantiatedFrom = Node.getInstantiatedFromMemberFunction(); + return InnerMatcher.matches(InstantiatedFrom ? *InstantiatedFrom : Node, + Finder, Builder); +} + +} // namespace + +UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckedFunctions(Options.get("CheckedFunctions", + "::std::async;" + "::std::launder;" + "::std::remove;" + "::std::remove_if;" + "::std::unique;" + "::std::unique_ptr::release;" + "::std::basic_string::empty;" + "::std::vector::empty")) {} + +void UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckedFunctions", CheckedFunctions); +} + +void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) { + auto FunVec = utils::options::parseStringList(CheckedFunctions); + auto MatchedCallExpr = expr(ignoringImplicit(ignoringParenImpCasts( + callExpr(callee(functionDecl( + // Don't match void overloads of checked functions. + unless(returns(voidType())), + isInstantiatedFrom(hasAnyName( + std::vector(FunVec.begin(), FunVec.end())))))) + .bind("match")))); + + auto UnusedInCompoundStmt = + compoundStmt(forEach(MatchedCallExpr), + // The checker can't currently differentiate between the + // return statement and other statements inside GNU statement + // expressions, so disable the checker inside them to avoid + // false positives. + unless(hasParent(stmtExpr()))); + auto UnusedInIfStmt = + ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr))); + auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr)); + auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr)); + auto UnusedInForStmt = + forStmt(eachOf(hasLoopInit(MatchedCallExpr), + hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr))); + auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr)); + auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr)); + + Finder->addMatcher( + stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt, + UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt, + UnusedInCaseStmt)), + this); +} + +void UnusedReturnValueCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Matched = Result.Nodes.getNodeAs("match")) { + diag(Matched->getLocStart(), + "the value returned by this function should be used") + << Matched->getSourceRange(); + diag(Matched->getLocStart(), + "cast the expression to void to silence this warning", + DiagnosticIDs::Note); + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/UnusedReturnValueCheck.h b/clang-tidy/bugprone/UnusedReturnValueCheck.h new file mode 100644 index 000000000..9475f56bc --- /dev/null +++ b/clang-tidy/bugprone/UnusedReturnValueCheck.h @@ -0,0 +1,39 @@ +//===--- UnusedReturnValueCheck.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_UNUSEDRETURNVALUECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRETURNVALUECHECK_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { +namespace bugprone { + +/// Detects function calls where the return value is unused. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-unused-return-value.html +class UnusedReturnValueCheck : public ClangTidyCheck { +public: + UnusedReturnValueCheck(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: + std::string CheckedFunctions; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNUSEDRETURNVALUECHECK_H diff --git a/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tidy/bugprone/UseAfterMoveCheck.cpp new file mode 100644 index 000000000..6fa4cab3c --- /dev/null +++ b/clang-tidy/bugprone/UseAfterMoveCheck.cpp @@ -0,0 +1,434 @@ +//===--- 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 bugprone { + +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(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/UseAfterMoveCheck.h b/clang-tidy/bugprone/UseAfterMoveCheck.h new file mode 100644 index 000000000..f6dea68d2 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_USEAFTERMOVECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_USEAFTERMOVECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// 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/bugprone-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 bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_USEAFTERMOVECHECK_H diff --git a/clang-tidy/bugprone/VirtualNearMissCheck.cpp b/clang-tidy/bugprone/VirtualNearMissCheck.cpp new file mode 100644 index 000000000..bb9c9b65c --- /dev/null +++ b/clang-tidy/bugprone/VirtualNearMissCheck.cpp @@ -0,0 +1,276 @@ +//===--- 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 bugprone { + +namespace { +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { + return Node.isOverloadedOperator(); +} +} // namespace + +/// 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(), EditDistanceThreshold); + 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 bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/bugprone/VirtualNearMissCheck.h b/clang-tidy/bugprone/VirtualNearMissCheck.h new file mode 100644 index 000000000..ea1e25660 --- /dev/null +++ b/clang-tidy/bugprone/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_BUGPRONE_VIRTUAL_NEAR_MISS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_VIRTUAL_NEAR_MISS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// \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/bugprone-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. + llvm::DenseMap PossibleMap; + + /// Key: + /// Value: whether the base method is overridden by some method in the derived + /// class. + llvm::DenseMap, bool> + OverriddenMap; + + const unsigned EditDistanceThreshold = 1; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_VIRTUAL_NEAR_MISS_H diff --git a/clang-tidy/cert/CERTTidyModule.cpp b/clang-tidy/cert/CERTTidyModule.cpp new file mode 100644 index 000000000..da09932e2 --- /dev/null +++ b/clang-tidy/cert/CERTTidyModule.cpp @@ -0,0 +1,95 @@ +//===--- 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/NewDeleteOverloadsCheck.h" +#include "../misc/NonCopyableObjects.h" +#include "../misc/StaticAssertCheck.h" +#include "../misc/ThrowByValueCatchByReferenceCheck.h" +#include "../performance/MoveConstructorInitCheck.h" +#include "CommandProcessorCheck.h" +#include "DontModifyStdNamespaceCheck.h" +#include "FloatLoopCounter.h" +#include "LimitedRandomnessCheck.h" +#include "PostfixOperatorCheck.h" +#include "ProperlySeededRandomGeneratorCheck.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"); + CheckFactories.registerCheck( + "cert-msc51-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"); + CheckFactories.registerCheck( + "cert-msc32-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..edc93c8ea --- /dev/null +++ b/clang-tidy/cert/CMakeLists.txt @@ -0,0 +1,28 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCERTModule + CERTTidyModule.cpp + CommandProcessorCheck.cpp + DontModifyStdNamespaceCheck.cpp + FloatLoopCounter.cpp + LimitedRandomnessCheck.cpp + PostfixOperatorCheck.cpp + ProperlySeededRandomGeneratorCheck.cpp + SetLongJmpCheck.cpp + StaticObjectExceptionCheck.cpp + StrToNumCheck.cpp + ThrownExceptionTypeCheck.cpp + VariadicFunctionDefCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyGoogleModule + clangTidyMiscModule + clangTidyPerformanceModule + 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/ProperlySeededRandomGeneratorCheck.cpp b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.cpp new file mode 100644 index 000000000..256525e35 --- /dev/null +++ b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.cpp @@ -0,0 +1,124 @@ +//===--- ProperlySeededRandomGeneratorCheck.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 "ProperlySeededRandomGeneratorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +ProperlySeededRandomGeneratorCheck::ProperlySeededRandomGeneratorCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawDisallowedSeedTypes( + Options.get("DisallowedSeedTypes", "time_t,std::time_t")) { + StringRef(RawDisallowedSeedTypes).split(DisallowedSeedTypes, ','); +} + +void ProperlySeededRandomGeneratorCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "DisallowedSeedTypes", RawDisallowedSeedTypes); +} + +void ProperlySeededRandomGeneratorCheck::registerMatchers(MatchFinder *Finder) { + auto RandomGeneratorEngineDecl = cxxRecordDecl(hasAnyName( + "::std::linear_congruential_engine", "::std::mersenne_twister_engine", + "::std::subtract_with_carry_engine", "::std::discard_block_engine", + "::std::independent_bits_engine", "::std::shuffle_order_engine")); + auto RandomGeneratorEngineTypeMatcher = hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(RandomGeneratorEngineDecl)))); + + // std::mt19937 engine; + // engine.seed(); + // ^ + // engine.seed(1); + // ^ + // const int x = 1; + // engine.seed(x); + // ^ + Finder->addMatcher( + cxxMemberCallExpr( + has(memberExpr(has(declRefExpr(RandomGeneratorEngineTypeMatcher)), + member(hasName("seed")), + unless(hasDescendant(cxxThisExpr()))))) + .bind("seed"), + this); + + // std::mt19937 engine; + // ^ + // std::mt19937 engine(1); + // ^ + // const int x = 1; + // std::mt19937 engine(x); + // ^ + Finder->addMatcher( + cxxConstructExpr(RandomGeneratorEngineTypeMatcher).bind("ctor"), this); + + // srand(); + // ^ + // const int x = 1; + // srand(x); + // ^ + Finder->addMatcher( + callExpr(callee(functionDecl(hasAnyName("::srand", "::std::srand")))) + .bind("srand"), + this); +} + +void ProperlySeededRandomGeneratorCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Ctor = Result.Nodes.getNodeAs("ctor"); + if (Ctor) + checkSeed(Result, Ctor); + + const auto *Func = Result.Nodes.getNodeAs("seed"); + if (Func) + checkSeed(Result, Func); + + const auto *Srand = Result.Nodes.getNodeAs("srand"); + if (Srand) + checkSeed(Result, Srand); +} + +template +void ProperlySeededRandomGeneratorCheck::checkSeed( + const MatchFinder::MatchResult &Result, const T *Func) { + if (Func->getNumArgs() == 0 || Func->getArg(0)->isDefaultArgument()) { + diag(Func->getExprLoc(), + "random number generator seeded with a default argument will generate " + "a predictable sequence of values"); + return; + } + + llvm::APSInt Value; + if (Func->getArg(0)->EvaluateAsInt(Value, *Result.Context)) { + diag(Func->getExprLoc(), + "random number generator seeded with a constant value will generate a " + "predictable sequence of values"); + return; + } + + const std::string SeedType( + Func->getArg(0)->IgnoreCasts()->getType().getAsString()); + if (llvm::find(DisallowedSeedTypes, SeedType) != DisallowedSeedTypes.end()) { + diag(Func->getExprLoc(), + "random number generator seeded with a disallowed source of seed " + "value will generate a predictable sequence of values"); + return; + } +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h new file mode 100644 index 000000000..ac5507c9c --- /dev/null +++ b/clang-tidy/cert/ProperlySeededRandomGeneratorCheck.h @@ -0,0 +1,47 @@ +//===--- ProperlySeededRandomGeneratorCheck.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_PROPERLY_SEEDED_RANDOM_GENERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_PROPERLY_SEEDED_RANDOM_GENERATOR_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { +namespace cert { + +/// Random number generator must be seeded properly. +/// +/// A random number generator initialized with default value or a +/// constant expression is a security vulnerability. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-properly-seeded-random-generator.html +class ProperlySeededRandomGeneratorCheck : public ClangTidyCheck { +public: + ProperlySeededRandomGeneratorCheck(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: + template + void checkSeed(const ast_matchers::MatchFinder::MatchResult &Result, + const T *Func); + + std::string RawDisallowedSeedTypes; + SmallVector DisallowedSeedTypes; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_PROPERLY_SEEDED_RANDOM_GENERATOR_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/AvoidGotoCheck.cpp b/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp new file mode 100644 index 000000000..3e800cd59 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/AvoidGotoCheck.cpp @@ -0,0 +1,57 @@ +//===--- AvoidGotoCheck.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 "AvoidGotoCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { +AST_MATCHER(GotoStmt, isForwardJumping) { + return Node.getLocStart() < Node.getLabel()->getLocStart(); +} +} // namespace + +void AvoidGotoCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // TODO: This check does not recognize `IndirectGotoStmt` which is a + // GNU extension. These must be matched separately and an AST matcher + // is currently missing for them. + + // Check if the 'goto' is used for control flow other than jumping + // out of a nested loop. + auto Loop = stmt(anyOf(forStmt(), cxxForRangeStmt(), whileStmt(), doStmt())); + auto NestedLoop = + stmt(anyOf(forStmt(hasAncestor(Loop)), cxxForRangeStmt(hasAncestor(Loop)), + whileStmt(hasAncestor(Loop)), doStmt(hasAncestor(Loop)))); + + Finder->addMatcher(gotoStmt(anyOf(unless(hasAncestor(NestedLoop)), + unless(isForwardJumping()))) + .bind("goto"), + this); +} + +void AvoidGotoCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Goto = Result.Nodes.getNodeAs("goto"); + + diag(Goto->getGotoLoc(), "avoid using 'goto' for flow control") + << Goto->getSourceRange(); + diag(Goto->getLabel()->getLocStart(), "label defined here", + DiagnosticIDs::Note); +} +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h b/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h new file mode 100644 index 000000000..cf7dd76f5 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/AvoidGotoCheck.h @@ -0,0 +1,36 @@ +//===--- AvoidGotoCheck.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_AVOIDGOTOCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_AVOIDGOTOCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// The usage of ``goto`` for control flow is error prone and should be replaced +/// with looping constructs. Only forward jumps in nested loops are accepted. +// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-avoid-goto.html +class AvoidGotoCheck : public ClangTidyCheck { +public: + AvoidGotoCheck(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_AVOIDGOTOCHECK_H diff --git a/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tidy/cppcoreguidelines/CMakeLists.txt new file mode 100644 index 000000000..49fedd622 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -0,0 +1,32 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCppCoreGuidelinesModule + AvoidGotoCheck.cpp + CppCoreGuidelinesTidyModule.cpp + InterfacesGlobalInitCheck.cpp + NarrowingConversionsCheck.cpp + NoMallocCheck.cpp + OwningMemoryCheck.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..a03925946 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -0,0 +1,88 @@ +//===--- 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 "AvoidGotoCheck.h" +#include "InterfacesGlobalInitCheck.h" +#include "NarrowingConversionsCheck.h" +#include "NoMallocCheck.h" +#include "OwningMemoryCheck.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-avoid-goto"); + CheckFactories.registerCheck( + "cppcoreguidelines-interfaces-global-init"); + CheckFactories.registerCheck( + "cppcoreguidelines-narrowing-conversions"); + CheckFactories.registerCheck("cppcoreguidelines-no-malloc"); + CheckFactories.registerCheck( + "cppcoreguidelines-owning-memory"); + 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/NarrowingConversionsCheck.cpp b/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp new file mode 100644 index 000000000..2c5676e76 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp @@ -0,0 +1,70 @@ +//===--- NarrowingConversionsCheck.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 "NarrowingConversionsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +// FIXME: Check double -> float truncation. Pay attention to casts: +void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) { + // ceil() and floor() are guaranteed to return integers, even though the type + // is not integral. + const auto IsCeilFloorCall = callExpr(callee(functionDecl( + hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor")))); + + const auto IsFloatExpr = + expr(hasType(realFloatingPointType()), unless(IsCeilFloorCall)); + + // casts: + // i = 0.5; + // void f(int); f(0.5); + Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(isInteger()), + hasSourceExpression(IsFloatExpr), + unless(hasParent(castExpr())), + unless(isInTemplateInstantiation())) + .bind("cast"), + this); + + // Binary operators: + // i += 0.5; + Finder->addMatcher( + binaryOperator(isAssignmentOperator(), + // The `=` case generates an implicit cast which is covered + // by the previous matcher. + unless(hasOperatorName("=")), + hasLHS(hasType(isInteger())), hasRHS(IsFloatExpr), + unless(isInTemplateInstantiation())) + .bind("op"), + this); +} + +void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Op = Result.Nodes.getNodeAs("op")) { + if (Op->getLocStart().isMacroID()) + return; + diag(Op->getOperatorLoc(), "narrowing conversion from %0 to %1") + << Op->getRHS()->getType() << Op->getLHS()->getType(); + return; + } + const auto *Cast = Result.Nodes.getNodeAs("cast"); + if (Cast->getLocStart().isMacroID()) + return; + diag(Cast->getExprLoc(), "narrowing conversion from %0 to %1") + << Cast->getSubExpr()->getType() << Cast->getType(); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.h b/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.h new file mode 100644 index 000000000..35f58bc22 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.h @@ -0,0 +1,37 @@ +//===--- NarrowingConversionsCheck.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_NARROWING_CONVERSIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_NARROWING_CONVERSIONS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Checks for narrowing conversions, e.g: +/// int i = 0; +/// i += 0.1; +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.html +class NarrowingConversionsCheck : public ClangTidyCheck { +public: + NarrowingConversionsCheck(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_NARROWING_CONVERSIONS_H diff --git a/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp new file mode 100644 index 000000000..5a2b88246 --- /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())); +} +} // namespace + +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/OwningMemoryCheck.cpp b/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp new file mode 100644 index 000000000..6c86e053b --- /dev/null +++ b/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp @@ -0,0 +1,394 @@ +//===--- OwningMemoryCheck.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 "OwningMemoryCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +// FIXME: Copied from 'NoMallocCheck.cpp'. Has to be refactored into 'util' or +// something like that. +namespace { +Matcher hasAnyListedName(const std::string &FunctionNames) { + const std::vector NameList = + utils::options::parseStringList(FunctionNames); + return hasAnyName(std::vector(NameList.begin(), NameList.end())); +} +} // namespace + +void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "LegacyResourceProducers", LegacyResourceProducers); + Options.store(Opts, "LegacyResourceConsumers", LegacyResourceConsumers); +} + +/// Match common cases, where the owner semantic is relevant, like function +/// calls, delete expressions and others. +void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + const auto OwnerDecl = typeAliasTemplateDecl(hasName("::gsl::owner")); + const auto IsOwnerType = hasType(OwnerDecl); + + const auto LegacyCreatorFunctions = hasAnyListedName(LegacyResourceProducers); + const auto LegacyConsumerFunctions = + hasAnyListedName(LegacyResourceConsumers); + + // Legacy functions that are use for resource management but cannot be + // updated to use `gsl::owner<>`, like standard C memory management. + const auto CreatesLegacyOwner = + callExpr(callee(functionDecl(LegacyCreatorFunctions))); + // C-style functions like `::malloc()` sometimes create owners as void* + // which is expected to be cast to the correct type in C++. This case + // must be catched explicitly. + const auto LegacyOwnerCast = + castExpr(hasSourceExpression(CreatesLegacyOwner)); + // Functions that do manual resource management but cannot be updated to use + // owner. Best example is `::free()`. + const auto LegacyOwnerConsumers = functionDecl(LegacyConsumerFunctions); + + const auto CreatesOwner = + anyOf(cxxNewExpr(), + callExpr(callee( + functionDecl(returns(qualType(hasDeclaration(OwnerDecl)))))), + CreatesLegacyOwner, LegacyOwnerCast); + + const auto ConsideredOwner = eachOf(IsOwnerType, CreatesOwner); + + // Find delete expressions that delete non-owners. + Finder->addMatcher( + cxxDeleteExpr( + hasDescendant( + declRefExpr(unless(ConsideredOwner)).bind("deleted_variable"))) + .bind("delete_expr"), + this); + + // Ignoring the implicit casts is vital because the legacy owners do not work + // with the 'owner<>' annotation and therefore always implicitly cast to the + // legacy type (even 'void *'). + // + // Furthermore, legacy owner functions are assumed to use raw pointers for + // resources. This check assumes that all pointer arguments of a legacy + // functions shall be 'gsl::owner<>'. + Finder->addMatcher( + callExpr( + allOf(callee(LegacyOwnerConsumers), + hasAnyArgument(allOf(unless(ignoringImpCasts(ConsideredOwner)), + hasType(pointerType()))))) + .bind("legacy_consumer"), + this); + + // Matching assignment to owners, with the rhs not being an owner nor creating + // one. + Finder->addMatcher(binaryOperator(allOf(matchers::isAssignmentOperator(), + hasLHS(IsOwnerType), + hasRHS(unless(ConsideredOwner)))) + .bind("owner_assignment"), + this); + + // Matching initialization of owners with non-owners, nor creating owners. + Finder->addMatcher( + namedDecl( + varDecl(allOf(hasInitializer(unless(ConsideredOwner)), IsOwnerType)) + .bind("owner_initialization")), + this); + + const auto HasConstructorInitializerForOwner = + has(cxxConstructorDecl(forEachConstructorInitializer( + cxxCtorInitializer(allOf(isMemberInitializer(), forField(IsOwnerType), + withInitializer( + // Avoid templatesdeclaration with + // excluding parenListExpr. + allOf(unless(ConsideredOwner), + unless(parenListExpr()))))) + .bind("owner_member_initializer")))); + + // Match class member initialization that expects owners, but does not get + // them. + Finder->addMatcher(cxxRecordDecl(HasConstructorInitializerForOwner), this); + + // Matching on assignment operations where the RHS is a newly created owner, + // but the LHS is not an owner. + Finder->addMatcher( + binaryOperator(allOf(matchers::isAssignmentOperator(), + hasLHS(unless(IsOwnerType)), hasRHS(CreatesOwner))) + .bind("bad_owner_creation_assignment"), + this); + + // Matching on initialization operations where the initial value is a newly + // created owner, but the LHS is not an owner. + Finder->addMatcher( + namedDecl(varDecl(eachOf(allOf(hasInitializer(CreatesOwner), + unless(IsOwnerType)), + allOf(hasInitializer(ConsideredOwner), + hasType(autoType().bind("deduced_type"))))) + .bind("bad_owner_creation_variable")), + this); + + // Match on all function calls that expect owners as arguments, but didn't + // get them. + Finder->addMatcher( + callExpr(forEachArgumentWithParam( + expr(unless(ConsideredOwner)).bind("expected_owner_argument"), + parmVarDecl(IsOwnerType))), + this); + + // Matching for function calls where one argument is a created owner, but the + // parameter type is not an owner. + Finder->addMatcher(callExpr(forEachArgumentWithParam( + expr(CreatesOwner).bind("bad_owner_creation_argument"), + parmVarDecl(unless(IsOwnerType)) + .bind("bad_owner_creation_parameter"))), + this); + + // Matching on functions, that return an owner/resource, but don't declare + // their return type as owner. + Finder->addMatcher( + functionDecl( + allOf(hasDescendant(returnStmt(hasReturnValue(ConsideredOwner)) + .bind("bad_owner_return")), + unless(returns(qualType(hasDeclaration(OwnerDecl)))))) + .bind("function_decl"), + this); + + // Match on classes that have an owner as member, but don't declare a + // destructor to properly release the owner. + Finder->addMatcher( + cxxRecordDecl( + allOf( + has(fieldDecl(IsOwnerType).bind("undestructed_owner_member")), + anyOf(unless(has(cxxDestructorDecl())), + has(cxxDestructorDecl(anyOf(isDefaulted(), isDeleted())))))) + .bind("non_destructor_class"), + this); +} + +void OwningMemoryCheck::check(const MatchFinder::MatchResult &Result) { + const auto &Nodes = Result.Nodes; + + bool CheckExecuted = false; + CheckExecuted |= handleDeletion(Nodes); + CheckExecuted |= handleLegacyConsumers(Nodes); + CheckExecuted |= handleExpectedOwner(Nodes); + CheckExecuted |= handleAssignmentAndInit(Nodes); + CheckExecuted |= handleAssignmentFromNewOwner(Nodes); + CheckExecuted |= handleReturnValues(Nodes); + CheckExecuted |= handleOwnerMembers(Nodes); + + assert(CheckExecuted && + "None of the subroutines executed, logic error in matcher!"); +} + +bool OwningMemoryCheck::handleDeletion(const BoundNodes &Nodes) { + // Result of delete matchers. + const auto *DeleteStmt = Nodes.getNodeAs("delete_expr"); + const auto *DeletedVariable = + Nodes.getNodeAs("deleted_variable"); + + // Deletion of non-owners, with `delete variable;` + if (DeleteStmt) { + diag(DeleteStmt->getLocStart(), + "deleting a pointer through a type that is " + "not marked 'gsl::owner<>'; consider using a " + "smart pointer instead") + << DeletedVariable->getSourceRange(); + + // FIXME: The declaration of the variable that was deleted can be + // rewritten. + const ValueDecl *Decl = DeletedVariable->getDecl(); + diag(Decl->getLocStart(), "variable declared here", DiagnosticIDs::Note) + << Decl->getSourceRange(); + + return true; + } + return false; +} + +bool OwningMemoryCheck::handleLegacyConsumers(const BoundNodes &Nodes) { + // Result of matching for legacy consumer-functions like `::free()`. + const auto *LegacyConsumer = Nodes.getNodeAs("legacy_consumer"); + + // FIXME: `freopen` should be handled seperately because it takes the filename + // as a pointer, which should not be an owner. The argument that is an owner + // is known and the false positive coming from the filename can be avoided. + if (LegacyConsumer) { + diag(LegacyConsumer->getLocStart(), + "calling legacy resource function without passing a 'gsl::owner<>'") + << LegacyConsumer->getSourceRange(); + return true; + } + return false; +} + +bool OwningMemoryCheck::handleExpectedOwner(const BoundNodes &Nodes) { + // Result of function call matchers. + const auto *ExpectedOwner = Nodes.getNodeAs("expected_owner_argument"); + + // Expected function argument to be owner. + if (ExpectedOwner) { + diag(ExpectedOwner->getLocStart(), + "expected argument of type 'gsl::owner<>'; got %0") + << ExpectedOwner->getType() << ExpectedOwner->getSourceRange(); + return true; + } + return false; +} + +/// Assignment and initialization of owner variables. +bool OwningMemoryCheck::handleAssignmentAndInit(const BoundNodes &Nodes) { + const auto *OwnerAssignment = + Nodes.getNodeAs("owner_assignment"); + const auto *OwnerInitialization = + Nodes.getNodeAs("owner_initialization"); + const auto *OwnerInitializer = + Nodes.getNodeAs("owner_member_initializer"); + + // Assignments to owners. + if (OwnerAssignment) { + diag(OwnerAssignment->getLocStart(), + "expected assignment source to be of type 'gsl::owner<>'; got %0") + << OwnerAssignment->getRHS()->getType() + << OwnerAssignment->getSourceRange(); + return true; + } + + // Initialization of owners. + if (OwnerInitialization) { + diag(OwnerInitialization->getLocStart(), + "expected initialization with value of type 'gsl::owner<>'; got %0") + << OwnerInitialization->getAnyInitializer()->getType() + << OwnerInitialization->getSourceRange(); + return true; + } + + // Initializer of class constructors that initialize owners. + if (OwnerInitializer) { + diag(OwnerInitializer->getSourceLocation(), + "expected initialization of owner member variable with value of type " + "'gsl::owner<>'; got %0") + // FIXME: the expression from getInit has type 'void', but the type + // of the supplied argument would be of interest. + << OwnerInitializer->getInit()->getType() + << OwnerInitializer->getSourceRange(); + return true; + } + return false; +} + +/// Problematic assignment and initializations, since the assigned value is a +/// newly created owner. +bool OwningMemoryCheck::handleAssignmentFromNewOwner(const BoundNodes &Nodes) { + const auto *BadOwnerAssignment = + Nodes.getNodeAs("bad_owner_creation_assignment"); + const auto *BadOwnerInitialization = + Nodes.getNodeAs("bad_owner_creation_variable"); + + const auto *BadOwnerArgument = + Nodes.getNodeAs("bad_owner_creation_argument"); + const auto *BadOwnerParameter = + Nodes.getNodeAs("bad_owner_creation_parameter"); + + // Bad assignments to non-owners, where the RHS is a newly created owner. + if (BadOwnerAssignment) { + diag(BadOwnerAssignment->getLocStart(), + "assigning newly created 'gsl::owner<>' to non-owner %0") + << BadOwnerAssignment->getLHS()->getType() + << BadOwnerAssignment->getSourceRange(); + return true; + } + + // Bad initialization of non-owners, where the RHS is a newly created owner. + if (BadOwnerInitialization) { + diag(BadOwnerInitialization->getLocStart(), + "initializing non-owner %0 with a newly created 'gsl::owner<>'") + << BadOwnerInitialization->getType() + << BadOwnerInitialization->getSourceRange(); + + // FIXME: FixitHint to rewrite the type of the initialized variable + // as 'gsl::owner' + + // If the type of the variable was deduced, the wrapping owner typedef is + // eliminated, therefore the check emits a special note for that case. + if (Nodes.getNodeAs("deduced_type")) { + diag(BadOwnerInitialization->getLocStart(), + "type deduction did not result in an owner", DiagnosticIDs::Note); + } + return true; + } + + // Function call, where one arguments is a newly created owner, but the + // parameter type is not. + if (BadOwnerArgument) { + assert(BadOwnerParameter && + "parameter for the problematic argument not found"); + diag(BadOwnerArgument->getLocStart(), "initializing non-owner argument of " + "type %0 with a newly created " + "'gsl::owner<>'") + << BadOwnerParameter->getType() << BadOwnerArgument->getSourceRange(); + return true; + } + return false; +} + +bool OwningMemoryCheck::handleReturnValues(const BoundNodes &Nodes) { + // Function return statements, that are owners/resources, but the function + // declaration does not declare its return value as owner. + const auto *BadReturnType = Nodes.getNodeAs("bad_owner_return"); + const auto *Function = Nodes.getNodeAs("function_decl"); + + // Function return values, that should be owners but aren't. + if (BadReturnType) { + // The returned value is a resource or variable that was not annotated with + // owner<> and the function return type is not owner<>. + diag(BadReturnType->getLocStart(), + "returning a newly created resource of " + "type %0 or 'gsl::owner<>' from a " + "function whose return type is not 'gsl::owner<>'") + << Function->getReturnType() << BadReturnType->getSourceRange(); + + // FIXME: Rewrite the return type as 'gsl::owner' + return true; + } + return false; +} + +bool OwningMemoryCheck::handleOwnerMembers(const BoundNodes &Nodes) { + // Classes, that have owners as member, but do not declare destructors + // accordingly. + const auto *BadClass = Nodes.getNodeAs("non_destructor_class"); + + // Classes, that contains owners, but do not declare destructors. + if (BadClass) { + const auto *DeclaredOwnerMember = + Nodes.getNodeAs("undestructed_owner_member"); + assert(DeclaredOwnerMember && + "match on class with bad destructor but without a declared owner"); + + diag(DeclaredOwnerMember->getLocStart(), + "member variable of type 'gsl::owner<>' requires the class %0 to " + "implement a destructor to release the owned resource") + << BadClass; + return true; + } + return false; +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/OwningMemoryCheck.h b/clang-tidy/cppcoreguidelines/OwningMemoryCheck.h new file mode 100644 index 000000000..e68e4c09b --- /dev/null +++ b/clang-tidy/cppcoreguidelines/OwningMemoryCheck.h @@ -0,0 +1,63 @@ +//===--- OwningMemoryCheck.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_OWNING_MEMORY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_OWNING_MEMORY_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Checks for common use cases for gsl::owner and enforces the unique owner +/// nature of it whenever possible. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-owning-memory.html +class OwningMemoryCheck : public ClangTidyCheck { +public: + OwningMemoryCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + LegacyResourceProducers(Options.get( + "LegacyResourceProducers", "::malloc;::aligned_alloc;::realloc;" + "::calloc;::fopen;::freopen;::tmpfile")), + LegacyResourceConsumers(Options.get( + "LegacyResourceConsumers", "::free;::realloc;::freopen;::fclose")) { + } + + /// Make configuration of checker discoverable. + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + bool handleDeletion(const ast_matchers::BoundNodes &Nodes); + bool handleLegacyConsumers(const ast_matchers::BoundNodes &Nodes); + bool handleExpectedOwner(const ast_matchers::BoundNodes &Nodes); + bool handleAssignmentAndInit(const ast_matchers::BoundNodes &Nodes); + bool handleAssignmentFromNewOwner(const ast_matchers::BoundNodes &Nodes); + bool handleReturnValues(const ast_matchers::BoundNodes &Nodes); + bool handleOwnerMembers(const ast_matchers::BoundNodes &Nodes); + + /// List of old C-style functions that create resources. + /// Defaults to + /// `::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile`. + const std::string LegacyResourceProducers; + /// List of old C-style functions that consume or release resources. + /// Defaults to `::free;::realloc;::freopen;::fclose`. + const std::string LegacyResourceConsumers; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_OWNING_MEMORY_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp new file mode 100644 index 000000000..8b0f7ce11 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp @@ -0,0 +1,82 @@ +//===--- 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 { + +namespace { +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); +} +} // namespace + +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..a81496e1c --- /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.getLocalOrGlobal("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..dfbb51bed --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp @@ -0,0 +1,63 @@ +//===--- 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; + + const auto AllPointerTypes = anyOf( + hasType(pointerType()), hasType(autoType(hasDeducedType(pointerType()))), + hasType(decltypeType(hasUnderlyingType(pointerType())))); + + // Flag all operators +, -, +=, -=, ++, -- that result in a pointer + Finder->addMatcher( + binaryOperator( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("+="), hasOperatorName("-=")), + AllPointerTypes, + 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(AllPointerTypes, + 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..4d9be8151 --- /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). +template +void forEachField(const RecordDecl &Record, const T &Fields, Func &&Fn) { + for (const FieldDecl *F : Fields) { + if (F->isAnonymousStructOrUnion()) { + if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl()) + forEachField(*R, R->fields(), Fn); + } else { + Fn(F); + } + } +} + +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(), + [&](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(), [&](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(), + [&](const FieldDecl *F) { OrderedFields.push_back(F); }); + + // Collect all the fields we need to initialize, including indirect fields. + SmallPtrSet AllFieldsToInit; + forEachField(ClassDecl, FieldsToInit, + [&](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; + SmallPtrSet UnionsSeen; + forEachField(ClassDecl, OrderedFields, [&](const FieldDecl *F) { + if (!FieldsToInit.count(F)) + return; + // 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 until C++2a. + if (F->getType()->isEnumeralType() || + (!getLangOpts().CPlusPlus2a && F->isBitField())) + return; + if (!F->getParent()->isUnion() || UnionsSeen.insert(F->getParent()).second) + 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/fuchsia/CMakeLists.txt b/clang-tidy/fuchsia/CMakeLists.txt new file mode 100644 index 000000000..b69a5c064 --- /dev/null +++ b/clang-tidy/fuchsia/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyFuchsiaModule + DefaultArgumentsCheck.cpp + FuchsiaTidyModule.cpp + MultipleInheritanceCheck.cpp + OverloadedOperatorCheck.cpp + RestrictSystemIncludesCheck.cpp + StaticallyConstructedObjectsCheck.cpp + TrailingReturnCheck.cpp + VirtualInheritanceCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyGoogleModule + clangTidyUtils + ) diff --git a/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp b/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp new file mode 100644 index 000000000..493ce9a24 --- /dev/null +++ b/clang-tidy/fuchsia/DefaultArgumentsCheck.cpp @@ -0,0 +1,64 @@ +//===--- 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" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +void DefaultArgumentsCheck::registerMatchers(MatchFinder *Finder) { + // Calling a function which uses default arguments is disallowed. + Finder->addMatcher(cxxDefaultArgExpr().bind("stmt"), this); + // Declaring default parameters is disallowed. + Finder->addMatcher(parmVarDecl(hasDefaultArgument()).bind("decl"), this); +} + +void DefaultArgumentsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *S = + Result.Nodes.getNodeAs("stmt")) { + diag(S->getUsedLocation(), + "calling a function that uses a default argument is disallowed"); + diag(S->getParam()->getLocStart(), + "default parameter was declared here", + DiagnosticIDs::Note); + } else if (const ParmVarDecl *D = + Result.Nodes.getNodeAs("decl")) { + SourceRange DefaultArgRange = D->getDefaultArgRange(); + + if (DefaultArgRange.getEnd() != D->getLocEnd()) { + return; + } else if (DefaultArgRange.getBegin().isMacroID()) { + diag(D->getLocStart(), + "declaring a parameter with a default argument is disallowed"); + } else { + SourceLocation StartLocation = D->getName().empty() ? + D->getLocStart() : D->getLocation(); + + SourceRange RemovalRange(Lexer::getLocForEndOfToken( + StartLocation, 0, + *Result.SourceManager, + Result.Context->getLangOpts() + ), + DefaultArgRange.getEnd() + ); + + diag(D->getLocStart(), + "declaring a parameter with a default argument is disallowed") + << D + << FixItHint::CreateRemoval(RemovalRange); + } + } +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/DefaultArgumentsCheck.h b/clang-tidy/fuchsia/DefaultArgumentsCheck.h new file mode 100644 index 000000000..f54fd9641 --- /dev/null +++ b/clang-tidy/fuchsia/DefaultArgumentsCheck.h @@ -0,0 +1,35 @@ +//===--- 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_FUCHSIA_DEFAULT_ARGUMENTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_DEFAULT_ARGUMENTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Default arguments are not allowed in declared or called functions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-default-arguments.html +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 fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_DEFAULT_ARGUMENTS_H diff --git a/clang-tidy/fuchsia/FuchsiaTidyModule.cpp b/clang-tidy/fuchsia/FuchsiaTidyModule.cpp new file mode 100644 index 000000000..0d3bbfe74 --- /dev/null +++ b/clang-tidy/fuchsia/FuchsiaTidyModule.cpp @@ -0,0 +1,60 @@ +//===--- FuchsiaTidyModule.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 "DefaultArgumentsCheck.h" +#include "MultipleInheritanceCheck.h" +#include "OverloadedOperatorCheck.h" +#include "RestrictSystemIncludesCheck.h" +#include "StaticallyConstructedObjectsCheck.h" +#include "TrailingReturnCheck.h" +#include "VirtualInheritanceCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// This module is for Fuchsia-specific checks. +class FuchsiaModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "fuchsia-default-arguments"); + CheckFactories.registerCheck( + "fuchsia-header-anon-namespaces"); + CheckFactories.registerCheck( + "fuchsia-multiple-inheritance"); + CheckFactories.registerCheck( + "fuchsia-overloaded-operator"); + CheckFactories.registerCheck( + "fuchsia-restrict-system-includes"); + CheckFactories.registerCheck( + "fuchsia-statically-constructed-objects"); + CheckFactories.registerCheck( + "fuchsia-trailing-return"); + CheckFactories.registerCheck( + "fuchsia-virtual-inheritance"); + } +}; +// Register the FuchsiaTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("fuchsia-module", "Adds Fuchsia platform checks."); +} // namespace fuchsia + +// This anchor is used to force the linker to link in the generated object file +// and thus register the FuchsiaModule. +volatile int FuchsiaModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp new file mode 100644 index 000000000..35504c982 --- /dev/null +++ b/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp @@ -0,0 +1,132 @@ +//===--- MultipleInheritanceCheck.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 "MultipleInheritanceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +namespace { +AST_MATCHER(CXXRecordDecl, hasBases) { + if (Node.hasDefinition()) + return Node.getNumBases() > 0; + return false; +} +} // namespace + +// Adds a node (by name) to the interface map, if it was not present in the map +// previously. +void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node, + bool isInterface) { + assert(Node->getIdentifier()); + StringRef Name = Node->getIdentifier()->getName(); + InterfaceMap.insert(std::make_pair(Name, isInterface)); +} + +// Returns "true" if the boolean "isInterface" has been set to the +// interface status of the current Node. Return "false" if the +// interface status for the current node is not yet known. +bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node, + bool &isInterface) const { + assert(Node->getIdentifier()); + StringRef Name = Node->getIdentifier()->getName(); + llvm::StringMapConstIterator Pair = InterfaceMap.find(Name); + if (Pair == InterfaceMap.end()) + return false; + isInterface = Pair->second; + return true; +} + +bool MultipleInheritanceCheck::isCurrentClassInterface( + const CXXRecordDecl *Node) const { + // Interfaces should have no fields. + if (!Node->field_empty()) return false; + + // Interfaces should have exclusively pure methods. + return llvm::none_of(Node->methods(), [](const CXXMethodDecl *M) { + return M->isUserProvided() && !M->isPure() && !M->isStatic(); + }); +} + +bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) { + if (!Node->getIdentifier()) + return false; + + // Short circuit the lookup if we have analyzed this record before. + bool PreviousIsInterfaceResult; + if (getInterfaceStatus(Node, PreviousIsInterfaceResult)) + return PreviousIsInterfaceResult; + + // To be an interface, all base classes must be interfaces as well. + for (const auto &I : Node->bases()) { + if (I.isVirtual()) continue; + const auto *Ty = I.getType()->getAs(); + if (!Ty) continue; + const RecordDecl *D = Ty->getDecl()->getDefinition(); + if (!D) continue; + const auto *Base = cast(D); + if (!isInterface(Base)) { + addNodeToInterfaceMap(Node, false); + return false; + } + } + + bool CurrentClassIsInterface = isCurrentClassInterface(Node); + addNodeToInterfaceMap(Node, CurrentClassIsInterface); + return CurrentClassIsInterface; +} + +void MultipleInheritanceCheck::registerMatchers(MatchFinder *Finder) { + // Requires C++. + if (!getLangOpts().CPlusPlus) + return; + + // Match declarations which have bases. + Finder->addMatcher(cxxRecordDecl(hasBases()).bind("decl"), this); +} + +void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *D = Result.Nodes.getNodeAs("decl")) { + // Check against map to see if if the class inherits from multiple + // concrete classes + unsigned NumConcrete = 0; + for (const auto &I : D->bases()) { + if (I.isVirtual()) continue; + const auto *Ty = I.getType()->getAs(); + if (!Ty) continue; + const auto *Base = cast(Ty->getDecl()->getDefinition()); + if (!isInterface(Base)) NumConcrete++; + } + + // Check virtual bases to see if there is more than one concrete + // non-virtual base. + for (const auto &V : D->vbases()) { + const auto *Ty = V.getType()->getAs(); + if (!Ty) continue; + const auto *Base = cast(Ty->getDecl()->getDefinition()); + if (!isInterface(Base)) NumConcrete++; + } + + if (NumConcrete > 1) { + diag(D->getLocStart(), + "inheriting mulitple classes that aren't " + "pure virtual is discouraged"); + } + } +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/MultipleInheritanceCheck.h b/clang-tidy/fuchsia/MultipleInheritanceCheck.h new file mode 100644 index 000000000..6250e3ec0 --- /dev/null +++ b/clang-tidy/fuchsia/MultipleInheritanceCheck.h @@ -0,0 +1,48 @@ +//===--- MultipleInheritanceCheck.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_FUCHSIA_MULTIPLE_INHERITANCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_MULTIPLE_INHERITANCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Mulitple implementation inheritance is discouraged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-multiple-inheritance.html +class MultipleInheritanceCheck : public ClangTidyCheck { +public: + MultipleInheritanceCheck(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 { InterfaceMap.clear(); } + +private: + void addNodeToInterfaceMap(const CXXRecordDecl *Node, bool isInterface); + bool getInterfaceStatus(const CXXRecordDecl *Node, bool &isInterface) const; + bool isCurrentClassInterface(const CXXRecordDecl *Node) const; + bool isInterface(const CXXRecordDecl *Node); + + // Contains the identity of each named CXXRecord as an interface. This is + // used to memoize lookup speeds and improve performance from O(N^2) to O(N), + // where N is the number of classes. + llvm::StringMap InterfaceMap; +}; + +} // namespace fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_MULTIPLE_INHERITANCE_H diff --git a/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp b/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp new file mode 100644 index 000000000..847f7635d --- /dev/null +++ b/clang-tidy/fuchsia/OverloadedOperatorCheck.cpp @@ -0,0 +1,45 @@ +//===--- OverloadedOperatorCheck.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 "OverloadedOperatorCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +namespace { +AST_MATCHER(FunctionDecl, isFuchsiaOverloadedOperator) { + if (const auto *CXXMethodNode = dyn_cast(&Node)) { + if (CXXMethodNode->isCopyAssignmentOperator() || + CXXMethodNode->isMoveAssignmentOperator()) + return false; + } + return Node.isOverloadedOperator(); +} +} // namespace + +void OverloadedOperatorCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl(isFuchsiaOverloadedOperator()).bind("decl"), + this); +} + +void OverloadedOperatorCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("decl"); + assert(D && "No FunctionDecl captured!"); + + SourceLocation Loc = D->getLocStart(); + if (Loc.isValid()) + diag(Loc, "overloading %0 is disallowed") << D; +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/OverloadedOperatorCheck.h b/clang-tidy/fuchsia/OverloadedOperatorCheck.h new file mode 100644 index 000000000..a22e5ae5c --- /dev/null +++ b/clang-tidy/fuchsia/OverloadedOperatorCheck.h @@ -0,0 +1,35 @@ +//===--- OverloadedOperatorCheck.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_FUCHSIA_OVERLOADED_OPERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_OVERLOADED_OPERATOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Overloading operators is disallowed by the Fuchsia coding standard. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-overloaded-operator.html +class OverloadedOperatorCheck : public ClangTidyCheck { +public: + OverloadedOperatorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_OVERLOADED_OPERATOR_H diff --git a/clang-tidy/fuchsia/RestrictSystemIncludesCheck.cpp b/clang-tidy/fuchsia/RestrictSystemIncludesCheck.cpp new file mode 100644 index 000000000..4817e9041 --- /dev/null +++ b/clang-tidy/fuchsia/RestrictSystemIncludesCheck.cpp @@ -0,0 +1,118 @@ +//===--- RestrictSystemIncludesCheck.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 "RestrictSystemIncludesCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Path.h" +#include + +namespace clang { +namespace tidy { +namespace fuchsia { + +class RestrictedIncludesPPCallbacks : public PPCallbacks { +public: + explicit RestrictedIncludesPPCallbacks(RestrictSystemIncludesCheck &Check, + SourceManager &SM) + : 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, + SrcMgr::CharacteristicKind FileType) override; + void EndOfMainFile() override; + +private: + struct IncludeDirective { + IncludeDirective() = default; + IncludeDirective(SourceLocation Loc, CharSourceRange Range, + StringRef Filename, StringRef FullPath, bool IsInMainFile) + : Loc(Loc), Range(Range), IncludeFile(Filename), IncludePath(FullPath), + IsInMainFile(IsInMainFile) {} + + SourceLocation Loc; // '#' location in the include directive + CharSourceRange Range; // SourceRange for the file name + std::string IncludeFile; // Filename as a string + std::string IncludePath; // Full file path as a string + bool IsInMainFile; // Whether or not the include is in the main file + }; + + using FileIncludes = llvm::SmallVector; + llvm::SmallDenseMap IncludeDirectives; + + RestrictSystemIncludesCheck &Check; + SourceManager &SM; +}; + +void RestrictedIncludesPPCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported, + SrcMgr::CharacteristicKind FileType) { + if (!Check.contains(FileName) && SrcMgr::isSystem(FileType)) { + SmallString<256> FullPath; + llvm::sys::path::append(FullPath, SearchPath); + llvm::sys::path::append(FullPath, RelativePath); + // Bucket the allowed include directives by the id of the file they were + // declared in. + IncludeDirectives[SM.getFileID(HashLoc)].emplace_back( + HashLoc, FilenameRange, FileName, FullPath.str(), + SM.isInMainFile(HashLoc)); + } +} + +void RestrictedIncludesPPCallbacks::EndOfMainFile() { + for (const auto &Bucket : IncludeDirectives) { + const FileIncludes &FileDirectives = Bucket.second; + + // Emit fixits for all restricted includes. + for (const auto &Include : FileDirectives) { + // Fetch the length of the include statement from the start to just after + // the newline, for finding the end (including the newline). + unsigned ToLen = std::strcspn(SM.getCharacterData(Include.Loc), "\n") + 1; + CharSourceRange ToRange = CharSourceRange::getCharRange( + Include.Loc, Include.Loc.getLocWithOffset(ToLen)); + + if (!Include.IsInMainFile) { + auto D = Check.diag( + Include.Loc, + "system include %0 not allowed, transitively included from %1"); + D << Include.IncludeFile << SM.getFilename(Include.Loc); + D << FixItHint::CreateRemoval(ToRange); + continue; + } + auto D = Check.diag(Include.Loc, "system include %0 not allowed"); + D << Include.IncludeFile; + D << FixItHint::CreateRemoval(ToRange); + } + } +} + +void RestrictSystemIncludesCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique( + *this, Compiler.getSourceManager())); +} + +void RestrictSystemIncludesCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Includes", AllowedIncludes); +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/RestrictSystemIncludesCheck.h b/clang-tidy/fuchsia/RestrictSystemIncludesCheck.h new file mode 100644 index 000000000..d4e5ac1e7 --- /dev/null +++ b/clang-tidy/fuchsia/RestrictSystemIncludesCheck.h @@ -0,0 +1,48 @@ +//===--- RestrictSystemIncludesCheck.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_FUCHSIA_RESTRICTINCLUDESSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_RESTRICTINCLUDESSCHECK_H + +#include "../ClangTidy.h" +#include "../ClangTidyDiagnosticConsumer.h" +#include "../utils/OptionsUtils.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Checks for allowed includes and suggests removal of any others. If no +/// includes are specified, the check will exit without issuing any warnings. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-restrict-system-includes.html +class RestrictSystemIncludesCheck : public ClangTidyCheck { +public: + RestrictSystemIncludesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowedIncludes(Options.get("Includes", "*")), + AllowedIncludesGlobList(AllowedIncludes) {} + + void registerPPCallbacks(CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool contains(StringRef FileName) { + return AllowedIncludesGlobList.contains(FileName); + } + +private: + std::string AllowedIncludes; + GlobList AllowedIncludesGlobList; +}; + +} // namespace fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_RESTRICTINCLUDESSCHECK_H diff --git a/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp b/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp new file mode 100644 index 000000000..16a534b18 --- /dev/null +++ b/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.cpp @@ -0,0 +1,60 @@ +//===--- StaticallyConstructedObjectsCheck.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 "StaticallyConstructedObjectsCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +namespace { +AST_MATCHER(Expr, isConstantInitializer) { + return Node.isConstantInitializer(Finder->getASTContext(), false); +} + +AST_MATCHER(VarDecl, isGlobalStatic) { + return Node.getStorageDuration() == SD_Static && !Node.isLocalVarDecl(); +} +} // namespace + +void StaticallyConstructedObjectsCheck::registerMatchers(MatchFinder *Finder) { + // Constructing global, non-trivial objects with static storage is + // disallowed, unless the object is statically initialized with a constexpr + // constructor or has no explicit constructor. + + // Constexpr requires C++11 or later. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + varDecl(allOf( + // Match global, statically stored objects... + isGlobalStatic(), + // ... that have C++ constructors... + hasDescendant(cxxConstructExpr(unless(allOf( + // ... unless it is constexpr ... + hasDeclaration(cxxConstructorDecl(isConstexpr())), + // ... and is statically initialized. + isConstantInitializer())))))) + .bind("decl"), + this); +} + +void StaticallyConstructedObjectsCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *D = Result.Nodes.getNodeAs("decl")) + diag(D->getLocStart(), "static objects are disallowed; if possible, use a " + "constexpr constructor instead"); +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h b/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h new file mode 100644 index 000000000..6df9b1c2b --- /dev/null +++ b/clang-tidy/fuchsia/StaticallyConstructedObjectsCheck.h @@ -0,0 +1,37 @@ +//===--- StaticallyConstructedObjectsCheck.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_FUCHSIA_STATICALLY_CONSTRUCTED_OBJECTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_STATICALLY_CONSTRUCTED_OBJECTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Constructing global, non-trivial objects with static storage is +/// disallowed, unless the object is statically initialized with a constexpr +/// constructor or has no explicit constructor. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-statically-constructed-objects.html +class StaticallyConstructedObjectsCheck : public ClangTidyCheck { +public: + StaticallyConstructedObjectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_STATICALLY_CONSTRUCTED_OBJECTS_H diff --git a/clang-tidy/fuchsia/TrailingReturnCheck.cpp b/clang-tidy/fuchsia/TrailingReturnCheck.cpp new file mode 100644 index 000000000..7af0c9c60 --- /dev/null +++ b/clang-tidy/fuchsia/TrailingReturnCheck.cpp @@ -0,0 +1,52 @@ +//===--- TrailingReturnCheck.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 "TrailingReturnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +namespace { +AST_MATCHER(FunctionDecl, hasTrailingReturn) { + return Node.getType()->castAs()->hasTrailingReturn(); +} +} // namespace + +void TrailingReturnCheck::registerMatchers(MatchFinder *Finder) { + + // Requires C++11 or later. + if (!getLangOpts().CPlusPlus11) + return; + + // Functions that have trailing returns are disallowed, except for those + // using decltype specifiers and lambda with otherwise unutterable + // return types. + Finder->addMatcher( + functionDecl(allOf(hasTrailingReturn(), + unless(anyOf(returns(decltypeType()), + hasParent(cxxRecordDecl(isLambda())))))) + .bind("decl"), + this); +} + +void TrailingReturnCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *D = Result.Nodes.getNodeAs("decl")) + diag(D->getLocStart(), + "a trailing return type is disallowed for this type of declaration"); +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/TrailingReturnCheck.h b/clang-tidy/fuchsia/TrailingReturnCheck.h new file mode 100644 index 000000000..4a16c4e4a --- /dev/null +++ b/clang-tidy/fuchsia/TrailingReturnCheck.h @@ -0,0 +1,37 @@ +//===--- TrailingReturnCheck.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_FUCHSIA_TRAILING_RETURN_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_TRAILING_RETURN_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Functions that have trailing returns are disallowed, except for those +/// using decltype specifiers and lambda with otherwise unutterable +/// return types. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-trailing-return.html +class TrailingReturnCheck : public ClangTidyCheck { +public: + TrailingReturnCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_TRAILING_RETURN_H diff --git a/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp b/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp new file mode 100644 index 000000000..f3da2b6ef --- /dev/null +++ b/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp @@ -0,0 +1,43 @@ +//===--- VirtualInheritanceCheck.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 "VirtualInheritanceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace fuchsia { + +namespace { +AST_MATCHER(CXXRecordDecl, hasDirectVirtualBaseClass) { + if (!Node.hasDefinition()) return false; + if (!Node.getNumVBases()) return false; + for (const CXXBaseSpecifier &Base : Node.bases()) + if (Base.isVirtual()) return true; + return false; +} +} // namespace + +void VirtualInheritanceCheck::registerMatchers(MatchFinder *Finder) { + // Defining classes using direct virtual inheritance is disallowed. + Finder->addMatcher(cxxRecordDecl(hasDirectVirtualBaseClass()).bind("decl"), + this); +} + +void VirtualInheritanceCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *D = Result.Nodes.getNodeAs("decl")) + diag(D->getLocStart(), "direct virtual inheritance is disallowed"); +} + +} // namespace fuchsia +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/fuchsia/VirtualInheritanceCheck.h b/clang-tidy/fuchsia/VirtualInheritanceCheck.h new file mode 100644 index 000000000..b2c84c411 --- /dev/null +++ b/clang-tidy/fuchsia/VirtualInheritanceCheck.h @@ -0,0 +1,35 @@ +//===--- VirtualInheritanceCheck.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_FUCHSIA_VIRTUAL_INHERITANCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_VIRTUAL_INHERITANCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace fuchsia { + +/// Defining classes with virtual inheritance is disallowed. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/fuchsia-virtual-inheritance.html +class VirtualInheritanceCheck : public ClangTidyCheck { + public: + VirtualInheritanceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace fuchsia +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_FUCHSIA_VIRTUAL_INHERITANCE_H diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tidy/google/AvoidCStyleCastsCheck.cpp new file mode 100644 index 000000000..4c7c164b9 --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -0,0 +1,221 @@ +//===--- 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++. + // We also disable it for Objective-C++. + if (!getLangOpts().CPlusPlus || getLangOpts().ObjC1 || getLangOpts().ObjC2) + 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/AvoidThrowingObjCExceptionCheck.cpp b/clang-tidy/google/AvoidThrowingObjCExceptionCheck.cpp new file mode 100644 index 000000000..aa91ff0cb --- /dev/null +++ b/clang-tidy/google/AvoidThrowingObjCExceptionCheck.cpp @@ -0,0 +1,52 @@ +//===--- AvoidThrowingObjCExceptionCheck.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 "AvoidThrowingObjCExceptionCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace objc { + +void AvoidThrowingObjCExceptionCheck::registerMatchers(MatchFinder *Finder) { + // this check should only be applied to ObjC sources. + if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) { + return; + } + + Finder->addMatcher(objcThrowStmt().bind("throwStmt"), this); + Finder->addMatcher( + objcMessageExpr(anyOf(hasSelector("raise:format:"), + hasSelector("raise:format:arguments:")), + hasReceiverType(asString("NSException"))) + .bind("raiseException"), + this); +} + +void AvoidThrowingObjCExceptionCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedStmt = + Result.Nodes.getNodeAs("throwStmt"); + const auto *MatchedExpr = + Result.Nodes.getNodeAs("raiseException"); + auto SourceLoc = MatchedStmt == nullptr ? MatchedExpr->getSelectorStartLoc() + : MatchedStmt->getThrowLoc(); + diag(SourceLoc, + "pass in NSError ** instead of throwing exception to indicate " + "Objective-C errors"); +} + +} // namespace objc +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h b/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h new file mode 100644 index 000000000..9498226d7 --- /dev/null +++ b/clang-tidy/google/AvoidThrowingObjCExceptionCheck.h @@ -0,0 +1,39 @@ +//===--- AvoidThrowingObjCExceptionCheck.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_OBJC_AVOID_THROWING_EXCEPTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_AVOID_THROWING_EXCEPTION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace objc { + +/// The check is to find usage of @throw invocation in Objective-C code. +/// We should avoid using @throw for Objective-C exceptions according to +/// the Google Objective-C Style Guide. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/google-objc-avoid-throwing-exception.html +class AvoidThrowingObjCExceptionCheck : public ClangTidyCheck { + public: + AvoidThrowingObjCExceptionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace objc +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_AVOID_THROWING_EXCEPTION_H diff --git a/clang-tidy/google/CMakeLists.txt b/clang-tidy/google/CMakeLists.txt new file mode 100644 index 000000000..1a7dd8712 --- /dev/null +++ b/clang-tidy/google/CMakeLists.txt @@ -0,0 +1,27 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyGoogleModule + AvoidCStyleCastsCheck.cpp + AvoidThrowingObjCExceptionCheck.cpp + DefaultArgumentsCheck.cpp + ExplicitConstructorCheck.cpp + ExplicitMakePairCheck.cpp + GlobalNamesInHeadersCheck.cpp + GlobalVariableDeclarationCheck.cpp + GoogleTidyModule.cpp + IntegerTypesCheck.cpp + NonConstReferences.cpp + OverloadedUnaryAndCheck.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..45c5b7061 --- /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", utils::defaultHeaderFileExtensions())) { + 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/GlobalVariableDeclarationCheck.cpp b/clang-tidy/google/GlobalVariableDeclarationCheck.cpp new file mode 100644 index 000000000..928d33486 --- /dev/null +++ b/clang-tidy/google/GlobalVariableDeclarationCheck.cpp @@ -0,0 +1,99 @@ +//===--- GlobalVariableDeclarationCheck.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 "GlobalVariableDeclarationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace objc { + +namespace { + +AST_MATCHER(VarDecl, isLocalVariable) { + return Node.isLocalVarDecl(); +} + +FixItHint generateFixItHint(const VarDecl *Decl, bool IsConst) { + char FC = Decl->getName()[0]; + if (!llvm::isAlpha(FC) || Decl->getName().size() == 1) { + // No fix available if first character is not alphabetical character, or it + // is a single-character variable, since it is difficult to determine the + // proper fix in this case. Users should create a proper variable name by + // their own. + return FixItHint(); + } + char SC = Decl->getName()[1]; + if ((FC == 'k' || FC == 'g') && !llvm::isAlpha(SC)) { + // No fix available if the prefix is correct but the second character is not + // alphabetical, since it is difficult to determine the proper fix in this + // case. + return FixItHint(); + } + auto NewName = (IsConst ? "k" : "g") + + llvm::StringRef(std::string(1, FC)).upper() + + Decl->getName().substr(1).str(); + return FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), + llvm::StringRef(NewName)); +} +} // namespace + +void GlobalVariableDeclarationCheck::registerMatchers(MatchFinder *Finder) { + // The relevant Style Guide rule only applies to Objective-C. + if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) { + return; + } + // need to add two matchers since we need to bind different ids to distinguish + // constants and variables. Since bind() can only be called on node matchers, + // we cannot make it in one matcher. + // + // Note that hasGlobalStorage() matches static variables declared locally + // inside a function or method, so we need to exclude those with + // isLocalVariable(). + Finder->addMatcher( + varDecl(hasGlobalStorage(), unless(hasType(isConstQualified())), + unless(isLocalVariable()), unless(matchesName("::g[A-Z]"))) + .bind("global_var"), + this); + Finder->addMatcher(varDecl(hasGlobalStorage(), hasType(isConstQualified()), + unless(isLocalVariable()), + unless(matchesName("::(k[A-Z]|[A-Z]{2,})"))) + .bind("global_const"), + this); +} + +void GlobalVariableDeclarationCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs("global_var")) { + diag(Decl->getLocation(), + "non-const global variable '%0' must have a name which starts with " + "'g[A-Z]'") + << Decl->getName() << generateFixItHint(Decl, false); + } + if (const auto *Decl = Result.Nodes.getNodeAs("global_const")) { + diag(Decl->getLocation(), + "const global variable '%0' must have a name which starts with " + "an appropriate prefix") + << Decl->getName() << generateFixItHint(Decl, true); + } +} + +} // namespace objc +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/GlobalVariableDeclarationCheck.h b/clang-tidy/google/GlobalVariableDeclarationCheck.h new file mode 100644 index 000000000..ed0352bb8 --- /dev/null +++ b/clang-tidy/google/GlobalVariableDeclarationCheck.h @@ -0,0 +1,39 @@ +//===--- GlobalVariableDeclarationCheck.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_OBJC_GLOBAL_VARIABLE_DECLARATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace objc { + +/// The check for Objective-C global variables and constants naming convention. +/// The declaration should follow the patterns of 'k[A-Z].*' (constants) or +/// 'g[A-Z].*' (variables). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/google-objc-global-variable-declaration.html +class GlobalVariableDeclarationCheck : public ClangTidyCheck { + public: + GlobalVariableDeclarationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace objc +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H diff --git a/clang-tidy/google/GoogleTidyModule.cpp b/clang-tidy/google/GoogleTidyModule.cpp new file mode 100644 index 000000000..f4f4118bb --- /dev/null +++ b/clang-tidy/google/GoogleTidyModule.cpp @@ -0,0 +1,98 @@ +//===--- 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 "AvoidCStyleCastsCheck.h" +#include "AvoidThrowingObjCExceptionCheck.h" +#include "DefaultArgumentsCheck.h" +#include "ExplicitConstructorCheck.h" +#include "ExplicitMakePairCheck.h" +#include "GlobalNamesInHeadersCheck.h" +#include "GlobalVariableDeclarationCheck.h" +#include "IntegerTypesCheck.h" +#include "NonConstReferences.h" +#include "OverloadedUnaryAndCheck.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-global-names-in-headers"); + CheckFactories.registerCheck( + "google-objc-avoid-throwing-exception"); + CheckFactories.registerCheck( + "google-objc-global-variable-declaration"); + CheckFactories.registerCheck( + "google-runtime-int"); + CheckFactories.registerCheck( + "google-runtime-operator"); + CheckFactories.registerCheck( + "google-runtime-references"); + CheckFactories.registerCheck( + "google-readability-casting"); + CheckFactories.registerCheck( + "google-readability-todo"); + CheckFactories + .registerCheck( + "google-readability-braces-around-statements"); + CheckFactories.registerCheck( + "google-readability-function-size"); + CheckFactories + .registerCheck( + "google-readability-namespace-comments"); + } + + 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..7f0b76581 --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.cpp @@ -0,0 +1,154 @@ +//===--- 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/AttrKinds.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; + // Match any integer types, unless they are passed to a printf-based API: + // + // http://google.github.io/styleguide/cppguide.html#64-bit_Portability + // "Where possible, avoid passing arguments of types specified by + // bitwidth typedefs to printf-based APIs." + Finder->addMatcher(typeLoc(loc(isInteger()), + unless(hasAncestor(callExpr( + callee(functionDecl(hasAttr(attr::Format))))))) + .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/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..32ff6e0b5 --- /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", utils::defaultHeaderFileExtensions())) { + 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..eeccf621a --- /dev/null +++ b/clang-tidy/hicpp/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyHICPPModule + ExceptionBaseclassCheck.cpp + MultiwayPathsCoveredCheck.cpp + NoAssemblerCheck.cpp + HICPPTidyModule.cpp + SignedBitwiseCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyBugproneModule + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyPerformanceModule + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp b/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp new file mode 100644 index 000000000..298759f86 --- /dev/null +++ b/clang-tidy/hicpp/ExceptionBaseclassCheck.cpp @@ -0,0 +1,50 @@ +//===--- ExceptionBaseclassCheck.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 "ExceptionBaseclassCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace hicpp { + +void ExceptionBaseclassCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxThrowExpr(allOf(has(expr(unless(hasType(qualType(hasCanonicalType( + hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom( + hasName("std::exception")))))))))), + has(expr(unless(cxxUnresolvedConstructExpr()))), + eachOf(has(expr(hasType(namedDecl().bind("decl")))), + anything()))) + .bind("bad_throw"), + this); +} + +void ExceptionBaseclassCheck::check(const MatchFinder::MatchResult &Result) { + const auto *BadThrow = Result.Nodes.getNodeAs("bad_throw"); + + diag(BadThrow->getSubExpr()->getLocStart(), "throwing an exception whose " + "type %0 is not derived from " + "'std::exception'") + << BadThrow->getSubExpr()->getType() << BadThrow->getSourceRange(); + + const auto *TypeDecl = Result.Nodes.getNodeAs("decl"); + if (TypeDecl != nullptr) + diag(TypeDecl->getLocStart(), "type defined here", DiagnosticIDs::Note); +} + +} // namespace hicpp +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/hicpp/ExceptionBaseclassCheck.h b/clang-tidy/hicpp/ExceptionBaseclassCheck.h new file mode 100644 index 000000000..778979dde --- /dev/null +++ b/clang-tidy/hicpp/ExceptionBaseclassCheck.h @@ -0,0 +1,35 @@ +//===--- ExceptionBaseclassCheck.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_EXCEPTION_BASECLASS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_EXCEPTION_BASECLASS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace hicpp { + +/// Check for thrown exceptions and enforce they are all derived from std::exception. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/hicpp-exception-baseclass.html +class ExceptionBaseclassCheck : public ClangTidyCheck { +public: + ExceptionBaseclassCheck(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_EXCEPTION_BASECLASS_H diff --git a/clang-tidy/hicpp/HICPPTidyModule.cpp b/clang-tidy/hicpp/HICPPTidyModule.cpp new file mode 100644 index 000000000..d2b9fc6dc --- /dev/null +++ b/clang-tidy/hicpp/HICPPTidyModule.cpp @@ -0,0 +1,119 @@ +//===------- 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 "../bugprone/UseAfterMoveCheck.h" +#include "../cppcoreguidelines/AvoidGotoCheck.h" +#include "../cppcoreguidelines/NoMallocCheck.h" +#include "../cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h" +#include "../cppcoreguidelines/ProTypeMemberInitCheck.h" +#include "../cppcoreguidelines/ProTypeVarargCheck.h" +#include "../cppcoreguidelines/SpecialMemberFunctionsCheck.h" +#include "../google/DefaultArgumentsCheck.h" +#include "../google/ExplicitConstructorCheck.h" +#include "../misc/NewDeleteOverloadsCheck.h" +#include "../misc/StaticAssertCheck.h" +#include "../bugprone/UndelegatedConstructorCheck.h" +#include "../modernize/DeprecatedHeadersCheck.h" +#include "../modernize/UseAutoCheck.h" +#include "../modernize/UseEmplaceCheck.h" +#include "../modernize/UseEqualsDefaultCheck.h" +#include "../modernize/UseEqualsDeleteCheck.h" +#include "../modernize/UseNoexceptCheck.h" +#include "../modernize/UseNullptrCheck.h" +#include "../modernize/UseOverrideCheck.h" +#include "../performance/MoveConstArgCheck.h" +#include "../performance/NoexceptMoveConstructorCheck.h" +#include "../readability/BracesAroundStatementsCheck.h" +#include "../readability/FunctionSizeCheck.h" +#include "../readability/IdentifierNamingCheck.h" +#include "ExceptionBaseclassCheck.h" +#include "MultiwayPathsCoveredCheck.h" +#include "NoAssemblerCheck.h" +#include "SignedBitwiseCheck.h" + +namespace clang { +namespace tidy { +namespace hicpp { + +class HICPPModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "hicpp-avoid-goto"); + CheckFactories.registerCheck( + "hicpp-braces-around-statements"); + CheckFactories.registerCheck( + "hicpp-deprecated-headers"); + CheckFactories.registerCheck( + "hicpp-exception-baseclass"); + CheckFactories.registerCheck( + "hicpp-multiway-paths-covered"); + CheckFactories.registerCheck("hicpp-signed-bitwise"); + 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-move-const-arg"); + CheckFactories.registerCheck( + "hicpp-new-delete-operators"); + CheckFactories.registerCheck( + "hicpp-noexcept-move"); + CheckFactories + .registerCheck( + "hicpp-no-array-decay"); + CheckFactories.registerCheck("hicpp-no-assembler"); + CheckFactories.registerCheck( + "hicpp-no-malloc"); + CheckFactories + .registerCheck( + "hicpp-special-member-functions"); + CheckFactories.registerCheck( + "hicpp-static-assert"); + CheckFactories.registerCheck("hicpp-use-auto"); + CheckFactories.registerCheck( + "hicpp-undelegated-constructor"); + CheckFactories.registerCheck( + "hicpp-use-emplace"); + CheckFactories.registerCheck( + "hicpp-use-equals-default"); + CheckFactories.registerCheck( + "hicpp-use-equals-delete"); + CheckFactories.registerCheck( + "hicpp-use-noexcept"); + CheckFactories.registerCheck( + "hicpp-use-nullptr"); + CheckFactories.registerCheck( + "hicpp-use-override"); + CheckFactories.registerCheck( + "hicpp-vararg"); + } +}; + +// 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/MultiwayPathsCoveredCheck.cpp b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp new file mode 100644 index 000000000..68ae54e0d --- /dev/null +++ b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.cpp @@ -0,0 +1,181 @@ +//===--- MultiwayPathsCoveredCheck.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 "MultiwayPathsCoveredCheck.h" +#include "clang/AST/ASTContext.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace hicpp { + +void MultiwayPathsCoveredCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnMissingElse", WarnOnMissingElse); +} + +void MultiwayPathsCoveredCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + switchStmt( + hasCondition(allOf( + // Match on switch statements that have either a bit-field or + // an integer condition. The ordering in 'anyOf()' is + // important because the last condition is the most general. + anyOf(ignoringImpCasts(memberExpr(hasDeclaration( + fieldDecl(isBitField()).bind("bitfield")))), + ignoringImpCasts(declRefExpr().bind("non-enum-condition"))), + // 'unless()' must be the last match here and must be bound, + // otherwise the matcher does not work correctly, because it + // will not explicitly ignore enum conditions. + unless(ignoringImpCasts( + declRefExpr(hasType(enumType())).bind("enum-condition")))))) + .bind("switch"), + this); + + // This option is noisy, therefore matching is configurable. + if (WarnOnMissingElse) { + Finder->addMatcher( + ifStmt(allOf(hasParent(ifStmt()), unless(hasElse(anything())))) + .bind("else-if"), + this); + } +} + +static std::pair countCaseLabels(const SwitchStmt *Switch) { + std::size_t CaseCount = 0; + bool HasDefault = false; + + const SwitchCase *CurrentCase = Switch->getSwitchCaseList(); + while (CurrentCase) { + ++CaseCount; + if (isa(CurrentCase)) + HasDefault = true; + + CurrentCase = CurrentCase->getNextSwitchCase(); + } + + return std::make_pair(CaseCount, HasDefault); +} + +/// This function calculate 2 ** Bits and returns +/// numeric_limits::max() if an overflow occured. +static std::size_t twoPow(std::size_t Bits) { + return Bits >= std::numeric_limits::digits + ? std::numeric_limits::max() + : static_cast(1) << Bits; +} + +/// Get the number of possible values that can be switched on for the type T. +/// +/// \return - 0 if bitcount could not be determined +/// - numeric_limits::max() when overflow appeared due to +/// more than 64 bits type size. +static std::size_t getNumberOfPossibleValues(QualType T, + const ASTContext &Context) { + // `isBooleanType` must come first because `bool` is an integral type as well + // and would not return 2 as result. + if (T->isBooleanType()) + return 2; + else if (T->isIntegralType(Context)) + return twoPow(Context.getTypeSize(T)); + else + return 1; +} + +void MultiwayPathsCoveredCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *ElseIfWithoutElse = + Result.Nodes.getNodeAs("else-if")) { + diag(ElseIfWithoutElse->getLocStart(), + "potentially uncovered codepath; add an ending else statement"); + return; + } + const auto *Switch = Result.Nodes.getNodeAs("switch"); + std::size_t SwitchCaseCount; + bool SwitchHasDefault; + std::tie(SwitchCaseCount, SwitchHasDefault) = countCaseLabels(Switch); + + // Checks the sanity of 'switch' statements that actually do define + // a default branch but might be degenerated by having no or only one case. + if (SwitchHasDefault) { + handleSwitchWithDefault(Switch, SwitchCaseCount); + return; + } + // Checks all 'switch' statements that do not define a default label. + // Here the heavy lifting happens. + if (!SwitchHasDefault && SwitchCaseCount > 0) { + handleSwitchWithoutDefault(Switch, SwitchCaseCount, Result); + return; + } + // Warns for degenerated 'switch' statements that neither define a case nor + // a default label. + // FIXME: Evaluate, if emitting a fix-it to simplify that statement is + // reasonable. + if (!SwitchHasDefault && SwitchCaseCount == 0) { + diag(Switch->getLocStart(), + "switch statement without labels has no effect"); + return; + } + llvm_unreachable("matched a case, that was not explicitly handled"); +} + +void MultiwayPathsCoveredCheck::handleSwitchWithDefault( + const SwitchStmt *Switch, std::size_t CaseCount) { + assert(CaseCount > 0 && "Switch statement with supposedly one default " + "branch did not contain any case labels"); + if (CaseCount == 1 || CaseCount == 2) + diag(Switch->getLocStart(), + CaseCount == 1 + ? "degenerated switch with default label only" + : "switch could be better written as an if/else statement"); +} + +void MultiwayPathsCoveredCheck::handleSwitchWithoutDefault( + const SwitchStmt *Switch, std::size_t CaseCount, + const MatchFinder::MatchResult &Result) { + // The matcher only works because some nodes are explicitly matched and + // bound but ignored. This is necessary to build the excluding logic for + // enums and 'switch' statements without a 'default' branch. + assert(!Result.Nodes.getNodeAs("enum-condition") && + "switch over enum is handled by warnings already, explicitly ignoring " + "them"); + // Determine the number of case labels. Because 'default' is not present + // and duplicating case labels is not allowed this number represents + // the number of codepaths. It can be directly compared to 'MaxPathsPossible' + // to see if some cases are missing. + // CaseCount == 0 is caught in DegenerateSwitch. Necessary because the + // matcher used for here does not match on degenerate 'switch'. + assert(CaseCount > 0 && "Switch statement without any case found. This case " + "should be excluded by the matcher and is handled " + "separatly."); + std::size_t MaxPathsPossible = [&]() { + if (const auto *GeneralCondition = + Result.Nodes.getNodeAs("non-enum-condition")) { + return getNumberOfPossibleValues(GeneralCondition->getType(), + *Result.Context); + } + if (const auto *BitfieldDecl = + Result.Nodes.getNodeAs("bitfield")) { + return twoPow(BitfieldDecl->getBitWidthValue(*Result.Context)); + } + + return static_cast(0); + }(); + + // FIXME: Transform the 'switch' into an 'if' for CaseCount == 1. + if (CaseCount < MaxPathsPossible) + diag(Switch->getLocStart(), + CaseCount == 1 ? "switch with only one case; use an if statement" + : "potential uncovered code path; add a default label"); +} +} // namespace hicpp +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/hicpp/MultiwayPathsCoveredCheck.h b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.h new file mode 100644 index 000000000..498dad650 --- /dev/null +++ b/clang-tidy/hicpp/MultiwayPathsCoveredCheck.h @@ -0,0 +1,51 @@ +//===--- MultiwayPathsCoveredCheck.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_MULTIWAY_PATHS_COVERED_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_MULTIWAY_PATHS_COVERED_H + +#include "../ClangTidy.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +namespace clang { +namespace tidy { +namespace hicpp { + +/// Find occasions where not all codepaths are explicitly covered in code. +/// This includes 'switch' without a 'default'-branch and 'if'-'else if'-chains +/// without a final 'else'-branch. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/hicpp-multiway-paths-covered.html +class MultiwayPathsCoveredCheck : public ClangTidyCheck { +public: + MultiwayPathsCoveredCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnMissingElse(Options.get("WarnOnMissingElse", 0)) {} + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleSwitchWithDefault(const SwitchStmt *Switch, std::size_t CaseCount); + void handleSwitchWithoutDefault( + const SwitchStmt *Switch, std::size_t CaseCount, + const ast_matchers::MatchFinder::MatchResult &Result); + /// This option can be configured to warn on missing 'else' branches in an + /// 'if-else if' chain. The default is false because this option might be + /// noisy on some code bases. + const bool WarnOnMissingElse; +}; + +} // namespace hicpp +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_MULTIWAY_PATHS_COVERED_H diff --git a/clang-tidy/hicpp/NoAssemblerCheck.cpp b/clang-tidy/hicpp/NoAssemblerCheck.cpp new file mode 100644 index 000000000..06969a873 --- /dev/null +++ b/clang-tidy/hicpp/NoAssemblerCheck.cpp @@ -0,0 +1,50 @@ +//===--- 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 tidy { +namespace hicpp { + +namespace { +AST_MATCHER(VarDecl, isAsm) { return Node.hasAttr(); } +const ast_matchers::internal::VariadicDynCastAllOfMatcher + fileScopeAsmDecl; +} // namespace + +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/hicpp/SignedBitwiseCheck.cpp b/clang-tidy/hicpp/SignedBitwiseCheck.cpp new file mode 100644 index 000000000..03900527e --- /dev/null +++ b/clang-tidy/hicpp/SignedBitwiseCheck.cpp @@ -0,0 +1,96 @@ +//===--- SignedBitwiseCheck.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 "SignedBitwiseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace hicpp { + +void SignedBitwiseCheck::registerMatchers(MatchFinder *Finder) { + const auto SignedIntegerOperand = + expr(ignoringImpCasts(hasType(isSignedInteger()))).bind("signed-operand"); + + // The standard [bitmask.types] allows some integral types to be implemented + // as signed types. Exclude these types from diagnosing for bitwise or(|) and + // bitwise and(&). Shifting and complementing such values is still not + // allowed. + const auto BitmaskType = namedDecl(anyOf( + hasName("::std::locale::category"), hasName("::std::ctype_base::mask"), + hasName("::std::ios_base::fmtflags"), hasName("::std::ios_base::iostate"), + hasName("::std::ios_base::openmode"))); + const auto IsStdBitmask = ignoringImpCasts(declRefExpr(hasType(BitmaskType))); + + // Match binary bitwise operations on signed integer arguments. + Finder->addMatcher( + binaryOperator( + allOf(anyOf(hasOperatorName("^"), hasOperatorName("|"), + hasOperatorName("&"), hasOperatorName("^="), + hasOperatorName("|="), hasOperatorName("&=")), + + unless(allOf(hasLHS(IsStdBitmask), hasRHS(IsStdBitmask))), + + hasEitherOperand(SignedIntegerOperand), + hasLHS(hasType(isInteger())), hasRHS(hasType(isInteger())))) + .bind("binary-no-sign-interference"), + this); + + // Shifting and complement is not allowed for any signed integer type because + // the sign bit may corrupt the result. + Finder->addMatcher( + binaryOperator( + allOf(anyOf(hasOperatorName("<<"), hasOperatorName(">>"), + hasOperatorName("<<="), hasOperatorName(">>=")), + hasEitherOperand(SignedIntegerOperand), + hasLHS(hasType(isInteger())), hasRHS(hasType(isInteger())))) + .bind("binary-sign-interference"), + this); + + // Match unary operations on signed integer types. + Finder->addMatcher(unaryOperator(allOf(hasOperatorName("~"), + hasUnaryOperand(SignedIntegerOperand))) + .bind("unary-signed"), + this); +} + +void SignedBitwiseCheck::check(const MatchFinder::MatchResult &Result) { + const ast_matchers::BoundNodes &N = Result.Nodes; + const auto *SignedOperand = N.getNodeAs("signed-operand"); + assert(SignedOperand && + "No signed operand found in problematic bitwise operations"); + + bool IsUnary = false; + SourceLocation Location; + + if (const auto *UnaryOp = N.getNodeAs("unary-signed")) { + IsUnary = true; + Location = UnaryOp->getLocStart(); + } else { + if (const auto *BinaryOp = + N.getNodeAs("binary-no-sign-interference")) + Location = BinaryOp->getLocStart(); + else if (const auto *BinaryOp = + N.getNodeAs("binary-sign-interference")) + Location = BinaryOp->getLocStart(); + else + llvm_unreachable("unexpected matcher result"); + } + diag(Location, "use of a signed integer operand with a " + "%select{binary|unary}0 bitwise operator") + << IsUnary << SignedOperand->getSourceRange(); +} + +} // namespace hicpp +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/hicpp/SignedBitwiseCheck.h b/clang-tidy/hicpp/SignedBitwiseCheck.h new file mode 100644 index 000000000..24338ad9a --- /dev/null +++ b/clang-tidy/hicpp/SignedBitwiseCheck.h @@ -0,0 +1,36 @@ +//===--- SignedBitwiseCheck.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_SIGNED_BITWISE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_HICPP_SIGNED_BITWISE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace hicpp { + +/// This check implements the rule 5.6.1 of the HICPP Standard, which disallows +/// bitwise operations on signed integer types. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/hicpp-signed-bitwise.html +class SignedBitwiseCheck : public ClangTidyCheck { +public: + SignedBitwiseCheck(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_SIGNED_BITWISE_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..c0e449ed5 --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -0,0 +1,55 @@ +//===--- 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) {} + +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..ca2d7efd0 --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.h @@ -0,0 +1,40 @@ +//===--- 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); + + bool shouldSuggestEndifComment(StringRef Filename) override { return false; } + std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; +}; + +} // 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..f1fdb39c2 --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.cpp @@ -0,0 +1,181 @@ +//===--- 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, + SrcMgr::CharacteristicKind FileType) 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, + SrcMgr::CharacteristicKind FileType) { + // 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/CMakeLists.txt b/clang-tidy/misc/CMakeLists.txt new file mode 100644 index 000000000..3806398e6 --- /dev/null +++ b/clang-tidy/misc/CMakeLists.txt @@ -0,0 +1,27 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyMiscModule + MisplacedConstCheck.cpp + UnconventionalAssignOperatorCheck.cpp + DefinitionsInHeadersCheck.cpp + MiscTidyModule.cpp + NewDeleteOverloadsCheck.cpp + NonCopyableObjects.cpp + RedundantExpressionCheck.cpp + StaticAssertCheck.cpp + ThrowByValueCatchByReferenceCheck.cpp + UniqueptrResetReleaseCheck.cpp + UnusedAliasDeclsCheck.cpp + UnusedParametersCheck.cpp + UnusedUsingDeclsCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.cpp b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp new file mode 100644 index 000000000..69ef3ef3a --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp @@ -0,0 +1,158 @@ +//===--- 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", utils::defaultHeaderFileExtensions())) { + 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. + // + // FIXME: Should declarations in anonymous namespaces get the same treatment + // as static / const declarations? + if (!ND->hasExternalFormalLinkage() && !ND->isInAnonymousNamespace()) + 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/MiscTidyModule.cpp b/clang-tidy/misc/MiscTidyModule.cpp new file mode 100644 index 000000000..241689a2b --- /dev/null +++ b/clang-tidy/misc/MiscTidyModule.cpp @@ -0,0 +1,69 @@ +//===--- 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 "DefinitionsInHeadersCheck.h" +#include "MisplacedConstCheck.h" +#include "NewDeleteOverloadsCheck.h" +#include "NonCopyableObjects.h" +#include "RedundantExpressionCheck.h" +#include "StaticAssertCheck.h" +#include "ThrowByValueCatchByReferenceCheck.h" +#include "UnconventionalAssignOperatorCheck.h" +#include "UniqueptrResetReleaseCheck.h" +#include "UnusedAliasDeclsCheck.h" +#include "UnusedParametersCheck.h" +#include "UnusedUsingDeclsCheck.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MiscModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("misc-misplaced-const"); + CheckFactories.registerCheck( + "misc-unconventional-assign-operator"); + CheckFactories.registerCheck( + "misc-definitions-in-headers"); + CheckFactories.registerCheck( + "misc-new-delete-overloads"); + CheckFactories.registerCheck( + "misc-non-copyable-objects"); + CheckFactories.registerCheck( + "misc-redundant-expression"); + CheckFactories.registerCheck("misc-static-assert"); + CheckFactories.registerCheck( + "misc-throw-by-value-catch-by-reference"); + CheckFactories.registerCheck( + "misc-uniqueptr-reset-release"); + CheckFactories.registerCheck( + "misc-unused-alias-decls"); + CheckFactories.registerCheck( + "misc-unused-parameters"); + CheckFactories.registerCheck( + "misc-unused-using-decls"); + } +}; + +} // 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/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/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..e259b6bfe --- /dev/null +++ b/clang-tidy/misc/RedundantExpressionCheck.cpp @@ -0,0 +1,1081 @@ +//===--- 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 + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace misc { +namespace { +using llvm::APSInt; + +static constexpr llvm::StringLiteral 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::CXXOperatorCallExprClass: + return cast(Left)->getOperator() == + cast(Right)->getOperator(); + 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::CXXFunctionalCastExprClass: + 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 cover 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 transformSubToCanonicalAddExpr(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()); +} + +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, ArrayRef, Names) { + const SourceManager &SM = Finder->getASTContext().getSourceManager(); + const LangOptions &LO = Finder->getASTContext().getLangOpts(); + SourceLocation Loc = Node.getExprLoc(); + while (Loc.isMacroID()) { + StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LO); + if (llvm::is_contained(Names, MacroName)) + return true; + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + return false; +} + +// Returns a matcher for integer constant expressions. +static ast_matchers::internal::Matcher +matchIntegerConstantExpr(StringRef Id) { + std::string CstId = (Id + "-const").str(); + return expr(isIntegerConstantExpr()).bind(CstId); +} + +// Retrieves the integer expression matched by 'matchIntegerConstantExpr' with +// name 'Id' and stores it into 'ConstExpr', the value of the expression is +// stored into `Value`. +static bool retrieveIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, APSInt &Value, + const Expr *&ConstExpr) { + std::string CstId = (Id + "-const").str(); + ConstExpr = Result.Nodes.getNodeAs(CstId); + return ConstExpr && ConstExpr->isIntegerConstantExpr(Value, *Result.Context); +} + +// Overloaded `retrieveIntegerConstantExpr` for compatibility. +static bool retrieveIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, APSInt &Value) { + const Expr *ConstExpr = nullptr; + return retrieveIntegerConstantExpr(Result, Id, Value, ConstExpr); +} + +// Returns a matcher for symbolic expressions (matches every expression except +// ingeter constant expressions). +static ast_matchers::internal::Matcher matchSymbolicExpr(StringRef Id) { + std::string SymId = (Id + "-sym").str(); + return ignoringParenImpCasts( + expr(unless(isIntegerConstantExpr())).bind(SymId)); +} + +// Retrieves the expression matched by 'matchSymbolicExpr' with name 'Id' and +// stores 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); +} + +// Retrieves 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 expressions: '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(); + std::string OverloadId = (Id + "-overload").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); + + // Do not bind to double negation. + const auto NegateNegateRelationalExpr = + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(unaryOperator( + hasOperatorName("!"), + hasUnaryOperand(anyOf(CastExpr, RelationalExpr))))); + + const auto OverloadedOperatorExpr = + cxxOperatorCallExpr( + anyOf(hasOverloadedOperatorName("=="), + hasOverloadedOperatorName("!="), hasOverloadedOperatorName("<"), + hasOverloadedOperatorName("<="), hasOverloadedOperatorName(">"), + hasOverloadedOperatorName(">=")), + // Filter noisy false positives. + unless(isMacro()), unless(isInTemplateInstantiation())) + .bind(OverloadId); + + return anyOf(RelationalExpr, CastExpr, NegateRelationalExpr, + NegateNegateRelationalExpr, OverloadedOperatorExpr); +} + +// Checks whether a function param is non constant reference type, and may +// be modified in the function. +static bool isNonConstReferenceType(QualType ParamType) { + return ParamType->isReferenceType() && + !ParamType.getNonReferenceType().isConstQualified(); +} + +// Checks whether the arguments of an overloaded operator can be modified in the +// function. +// For operators that take an instance and a constant as arguments, only the +// first argument (the instance) needs to be checked, since the constant itself +// is a temporary expression. Whether the second parameter is checked is +// controlled by the parameter `ParamsToCheckCount`. +static bool +canOverloadedOperatorArgsBeModified(const FunctionDecl *OperatorDecl, + bool checkSecondParam) { + unsigned ParamCount = OperatorDecl->getNumParams(); + + // Overloaded operators declared inside a class have only one param. + // These functions must be declared const in order to not be able to modify + // the instance of the class they are called through. + if (ParamCount == 1 && + !OperatorDecl->getType()->getAs()->isConst()) + return true; + + if (isNonConstReferenceType(OperatorDecl->getParamDecl(0)->getType())) + return true; + + return checkSecondParam && ParamCount == 2 && + isNonConstReferenceType(OperatorDecl->getParamDecl(1)->getType()); +} + +// Retrieves 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, const Expr *&ConstExpr) { + std::string CastId = (Id + "-cast").str(); + std::string SwapId = (Id + "-swap").str(); + std::string NegateId = (Id + "-negate").str(); + std::string OverloadId = (Id + "-overload").str(); + + if (const auto *Bin = Result.Nodes.getNodeAs(Id)) { + // Operand received with explicit comparator. + Opcode = Bin->getOpcode(); + OperandExpr = Bin; + + if (!retrieveIntegerConstantExpr(Result, Id, Value, ConstExpr)) + 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 if (const auto *OverloadedOperatorExpr = + Result.Nodes.getNodeAs(OverloadId)) { + const auto *OverloadedFunctionDecl = dyn_cast_or_null(OverloadedOperatorExpr->getCalleeDecl()); + if (!OverloadedFunctionDecl) + return false; + + if (canOverloadedOperatorArgsBeModified(OverloadedFunctionDecl, false)) + return false; + + if (canOverloadedOperatorArgsBeModified(OverloadedFunctionDecl, false)) + return false; + + if (!OverloadedOperatorExpr->getArg(1)->isIntegerConstantExpr( + Value, *Result.Context)) + return false; + + Symbol = OverloadedOperatorExpr->getArg(0); + OperandExpr = OverloadedOperatorExpr; + Opcode = BinaryOperator::getOverloadedOpcode(OverloadedOperatorExpr->getOperator()); + + return BinaryOperator::isComparisonOp(Opcode); + } 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; +} + +// Checks for expressions like (X == 4) && (Y != 9) +static bool areSidesBinaryConstExpressions(const BinaryOperator *&BinOp, const ASTContext *AstCtx) { + const auto *LhsBinOp = dyn_cast(BinOp->getLHS()); + const auto *RhsBinOp = dyn_cast(BinOp->getRHS()); + + if (!LhsBinOp || !RhsBinOp) + return false; + + if ((LhsBinOp->getLHS()->isIntegerConstantExpr(*AstCtx) || + LhsBinOp->getRHS()->isIntegerConstantExpr(*AstCtx)) && + (RhsBinOp->getLHS()->isIntegerConstantExpr(*AstCtx) || + RhsBinOp->getRHS()->isIntegerConstantExpr(*AstCtx))) + return true; + return false; +} + +// Retrieves integer constant subexpressions from binary operator expressions +// that have two equivalent sides. +// E.g.: from (X == 5) && (X == 5) retrieves 5 and 5. +static bool retrieveConstExprFromBothSides(const BinaryOperator *&BinOp, + BinaryOperatorKind &MainOpcode, + BinaryOperatorKind &SideOpcode, + const Expr *&LhsConst, + const Expr *&RhsConst, + const ASTContext *AstCtx) { + assert(areSidesBinaryConstExpressions(BinOp, AstCtx) && + "Both sides of binary operator must be constant expressions!"); + + MainOpcode = BinOp->getOpcode(); + + const auto *BinOpLhs = cast(BinOp->getLHS()); + const auto *BinOpRhs = cast(BinOp->getRHS()); + + LhsConst = BinOpLhs->getLHS()->isIntegerConstantExpr(*AstCtx) + ? BinOpLhs->getLHS() + : BinOpLhs->getRHS(); + RhsConst = BinOpRhs->getLHS()->isIntegerConstantExpr(*AstCtx) + ? BinOpRhs->getLHS() + : BinOpRhs->getRHS(); + + if (!LhsConst || !RhsConst) + return false; + + assert(BinOpLhs->getOpcode() == BinOpRhs->getOpcode() && + "Sides of the binary operator must be equivalent expressions!"); + + SideOpcode = BinOpLhs->getOpcode(); + + return true; +} + +static bool areExprsFromDifferentMacros(const Expr *LhsExpr, + const Expr *RhsExpr, + const ASTContext *AstCtx) { + if (!LhsExpr || !RhsExpr) + return false; + + SourceLocation LhsLoc = LhsExpr->getExprLoc(); + SourceLocation RhsLoc = RhsExpr->getExprLoc(); + + if (!LhsLoc.isMacroID() || !RhsLoc.isMacroID()) + return false; + + const SourceManager &SM = AstCtx->getSourceManager(); + const LangOptions &LO = AstCtx->getLangOpts(); + + return !(Lexer::getImmediateMacroName(LhsLoc, SM, LO) == + Lexer::getImmediateMacroName(RhsLoc, SM, LO)); +} + +static bool areExprsMacroAndNonMacro(const Expr *&LhsExpr, + const Expr *&RhsExpr) { + if (!LhsExpr || !RhsExpr) + return false; + + SourceLocation LhsLoc = LhsExpr->getExprLoc(); + SourceLocation RhsLoc = RhsExpr->getExprLoc(); + + return LhsLoc.isMacroID() != RhsLoc.isMacroID(); +} +} // namespace + +void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) { + const auto AnyLiteralExpr = ignoringParenImpCasts( + anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral())); + + const auto BannedIntegerLiteral = + integerLiteral(expandedByMacro(KnownBannedMacroNames)); + + // Binary with equivalent operands, like (X != 2 && X != 2). + 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); + + // Conditional (trenary) operator with equivalent operands, like (Y ? X : X). + Finder->addMatcher(conditionalOperator(expressionsAreEquivalent(), + // Filter noisy false positives. + unless(conditionalOperatorIsInMacro()), + unless(isInTemplateInstantiation())) + .bind("cond"), + this); + + // Overloaded operators with equivalent operands. + 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 expressions like: !(1 | 2 | 3) + Finder->addMatcher( + implicitCastExpr( + hasImplicitDestinationType(isInteger()), + has(unaryOperator( + hasOperatorName("!"), + hasUnaryOperand(ignoringParenImpCasts(binaryOperator( + anyOf(hasOperatorName("|"), hasOperatorName("&")), + hasLHS(anyOf(binaryOperator(anyOf(hasOperatorName("|"), + hasOperatorName("&"))), + integerLiteral())), + hasRHS(integerLiteral()))))) + .bind("logical-bitwise-confusion"))), + this); + + // Match expressions like: (X << 8) & 0xFF + Finder->addMatcher( + binaryOperator(hasOperatorName("&"), + hasEitherOperand(ignoringParenImpCasts(binaryOperator( + hasOperatorName("<<"), + hasRHS(ignoringParenImpCasts( + integerLiteral().bind("shift-const")))))), + hasEitherOperand(ignoringParenImpCasts( + integerLiteral().bind("and-const")))) + .bind("left-right-shift-confusion"), + 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; + + transformSubToCanonicalAddExpr(LhsOpcode, LhsValue); + transformSubToCanonicalAddExpr(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"); + } + } + } +} + +static bool exprEvaluatesToZero(BinaryOperatorKind Opcode, APSInt Value) { + return (Opcode == BO_And || Opcode == BO_AndAssign) && Value == 0; +} + +static bool exprEvaluatesToBitwiseNegatedZero(BinaryOperatorKind Opcode, + APSInt Value) { + return (Opcode == BO_Or || Opcode == BO_OrAssign) && ~Value == 0; +} + +static bool exprEvaluatesToSymbolic(BinaryOperatorKind Opcode, APSInt Value) { + return ((Opcode == BO_Or || Opcode == BO_OrAssign) && Value == 0) || + ((Opcode == BO_And || Opcode == BO_AndAssign) && ~Value == 0); +} + + +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"); + } + } else if (const auto *IneffectiveOperator = + Result.Nodes.getNodeAs( + "ineffective-bitwise")) { + APSInt Value; + const Expr *Sym = nullptr, *ConstExpr = nullptr; + + if (!retrieveSymbolicExpr(Result, "ineffective-bitwise", Sym) || + !retrieveIntegerConstantExpr(Result, "ineffective-bitwise", Value, + ConstExpr)) + return; + + if((Value != 0 && ~Value != 0) || Sym->getExprLoc().isMacroID()) + return; + + SourceLocation Loc = IneffectiveOperator->getOperatorLoc(); + + BinaryOperatorKind Opcode = IneffectiveOperator->getOpcode(); + if (exprEvaluatesToZero(Opcode, Value)) { + diag(Loc, "expression always evaluates to 0"); + } else if (exprEvaluatesToBitwiseNegatedZero(Opcode, Value)) { + SourceRange ConstExprRange(ConstExpr->getLocStart(), + ConstExpr->getLocEnd()); + StringRef ConstExprText = Lexer::getSourceText( + CharSourceRange::getTokenRange(ConstExprRange), *Result.SourceManager, + Result.Context->getLangOpts()); + + diag(Loc, "expression always evaluates to '%0'") << ConstExprText; + + } else if (exprEvaluatesToSymbolic(Opcode, Value)) { + SourceRange SymExprRange(Sym->getLocStart(), Sym->getLocEnd()); + + StringRef ExprText = Lexer::getSourceText( + CharSourceRange::getTokenRange(SymExprRange), *Result.SourceManager, + Result.Context->getLangOpts()); + + diag(Loc, "expression always evaluates to '%0'") << ExprText; + } + } +} + +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). + // E.g.: (X < 2) && (X > 4) + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + const Expr *LhsExpr = nullptr, *RhsExpr = nullptr; + const Expr *LhsSymbol = nullptr, *RhsSymbol = nullptr; + const Expr *LhsConst = nullptr, *RhsConst = nullptr; + BinaryOperatorKind LhsOpcode, RhsOpcode; + APSInt LhsValue, RhsValue; + + if (!retrieveRelationalIntegerConstantExpr( + Result, "lhs", LhsExpr, LhsOpcode, LhsSymbol, LhsValue, LhsConst) || + !retrieveRelationalIntegerConstantExpr( + Result, "rhs", RhsExpr, RhsOpcode, RhsSymbol, RhsValue, RhsConst) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + // Bring expr to a canonical form: smallest constant must be on the left. + if (APSInt::compareValues(LhsValue, RhsValue) > 0) { + std::swap(LhsExpr, RhsExpr); + std::swap(LhsValue, RhsValue); + std::swap(LhsSymbol, RhsSymbol); + std::swap(LhsOpcode, RhsOpcode); + } + + // Constants come from two different macros, or one of them is a macro. + if (areExprsFromDifferentMacros(LhsConst, RhsConst, Result.Context) || + areExprsMacroAndNonMacro(LhsConst, RhsConst)) + return; + + if ((Opcode == BO_LAnd || Opcode == BO_LOr) && + areEquivalentRanges(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "equivalent expression on both sides 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")) { + // If the expression's constants are macros, check whether they are + // intentional. + if (areSidesBinaryConstExpressions(BinOp, Result.Context)) { + const Expr *LhsConst = nullptr, *RhsConst = nullptr; + BinaryOperatorKind MainOpcode, SideOpcode; + + if (!retrieveConstExprFromBothSides(BinOp, MainOpcode, SideOpcode, + LhsConst, RhsConst, Result.Context)) + return; + + if (areExprsFromDifferentMacros(LhsConst, RhsConst, Result.Context) || + areExprsMacroAndNonMacro(LhsConst, RhsConst)) + return; + } + + diag(BinOp->getOperatorLoc(), "both sides of operator are equivalent"); + } + + if (const auto *CondOp = + Result.Nodes.getNodeAs("cond")) { + const Expr *TrueExpr = CondOp->getTrueExpr(); + const Expr *FalseExpr = CondOp->getFalseExpr(); + + if (areExprsFromDifferentMacros(TrueExpr, FalseExpr, Result.Context) || + areExprsMacroAndNonMacro(TrueExpr, FalseExpr)) + return; + diag(CondOp->getColonLoc(), + "'true' and 'false' expressions are equivalent"); + } + + if (const auto *Call = Result.Nodes.getNodeAs("call")) { + const auto *OverloadedFunctionDecl = dyn_cast_or_null(Call->getCalleeDecl()); + if (!OverloadedFunctionDecl) + return; + + if (canOverloadedOperatorArgsBeModified(OverloadedFunctionDecl, true)) + return; + + diag(Call->getOperatorLoc(), + "both sides of overloaded operator are equivalent"); + } + + if (const auto *NegateOperator = + Result.Nodes.getNodeAs("logical-bitwise-confusion")) { + SourceLocation OperatorLoc = NegateOperator->getOperatorLoc(); + + auto Diag = + diag(OperatorLoc, + "ineffective logical negation operator used; did you mean '~'?"); + SourceLocation LogicalNotLocation = OperatorLoc.getLocWithOffset(1); + + if (!LogicalNotLocation.isMacroID()) + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OperatorLoc, LogicalNotLocation), "~"); + } + + if (const auto *BinaryAndExpr = Result.Nodes.getNodeAs( + "left-right-shift-confusion")) { + const auto *ShiftingConst = Result.Nodes.getNodeAs("shift-const"); + assert(ShiftingConst && "Expr* 'ShiftingConst' is nullptr!"); + APSInt ShiftingValue; + + if (!ShiftingConst->isIntegerConstantExpr(ShiftingValue, *Result.Context)) + return; + + const auto *AndConst = Result.Nodes.getNodeAs("and-const"); + assert(AndConst && "Expr* 'AndCont' is nullptr!"); + APSInt AndValue; + if (!AndConst->isIntegerConstantExpr(AndValue, *Result.Context)) + return; + + // If ShiftingConst is shifted left with more bits than the position of the + // leftmost 1 in the bit representation of AndValue, AndConstant is + // ineffective. + if (AndValue.getActiveBits() > ShiftingValue) + return; + + auto Diag = diag(BinaryAndExpr->getOperatorLoc(), + "ineffective bitwise and operation"); + } + + // Check for the following bound expressions: + // - "binop-const-compare-to-sym", + // - "binop-const-compare-to-binop-const", + // Produced message: + // -> "logical expression is always false/true" + checkArithmeticExpr(Result); + + // Check for the following bound expression: + // - "binop-const-compare-to-const", + // - "ineffective-bitwise" + // Produced message: + // -> "logical expression is always false/true" + // -> "expression always evaluates to ..." + checkBitwiseExpr(Result); + + // Check for te following bound expression: + // - "comparisons-of-symbol-and-const", + // Produced messages: + // -> "equivalent expression on both sides of logical operator", + // -> "logical expression is always false/true" + // -> "expression is redundant" + 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..c0f8bf5ec --- /dev/null +++ b/clang-tidy/misc/RedundantExpressionCheck.h @@ -0,0 +1,41 @@ +//===--- 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 { + +/// The checker detects expressions that are redundant, because they contain +/// ineffective, useless parts. +/// +/// 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/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/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..0fd7e7712 --- /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()), + anyOf(autoType(), 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/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..1e342d1f6 --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.cpp @@ -0,0 +1,193 @@ +//===--- 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 "llvm/ADT/STLExtras.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), + StrictMode(Options.getLocalOrGlobal("StrictMode", 0) != 0) {} + +void UnusedParametersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StrictMode", StrictMode); +} + +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()); + // 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 CallExpr *Call : Indexer->getFnCalls(Function)) + if (ParamIndex < Call->getNumArgs()) // See PR38055 for example. + 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; + + // In non-strict mode ignore function definitions with empty bodies + // (constructor initializer counts for non-empty body). + if (StrictMode || + (Function->getBody()->child_begin() != + Function->getBody()->child_end()) || + (isa(Function) && + cast(Function)->getNumCtorInitializers() > 0)) + 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..b9bae26ff --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.h @@ -0,0 +1,43 @@ +//===--- 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; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool StrictMode; + 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/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/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..f02b2ae9d --- /dev/null +++ b/clang-tidy/modernize/CMakeLists.txt @@ -0,0 +1,42 @@ +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 + UseUncaughtExceptionsCheck.cpp + UseUsingCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/modernize/DeprecatedHeadersCheck.cpp b/clang-tidy/modernize/DeprecatedHeadersCheck.cpp new file mode 100644 index 000000000..1ff3f8988 --- /dev/null +++ b/clang-tidy/modernize/DeprecatedHeadersCheck.cpp @@ -0,0 +1,125 @@ +//===--- 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, + SrcMgr::CharacteristicKind FileType) 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, + SrcMgr::CharacteristicKind FileType) { + // 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..54bf941ff --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.cpp @@ -0,0 +1,920 @@ +//===--- 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(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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(), + hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl( + hasMethod(cxxMethodDecl(hasName("begin"), isConst())), + hasMethod(cxxMethodDecl(hasName("end"), + isConst())))) // hasDeclaration + ))), // qualType + qualType(unless(isConstQualified()), + hasUnqualifiedDesugaredType(recordType(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..541c2cb5a --- /dev/null +++ b/clang-tidy/modernize/MakeSharedCheck.cpp @@ -0,0 +1,32 @@ +//===--- 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(hasUnqualifiedDesugaredType( + recordType(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..8deaa83a2 --- /dev/null +++ b/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -0,0 +1,382 @@ +//===--- 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.getLocalOrGlobal("IncludeStyle", "llvm"))), + MakeSmartPtrFunctionHeader( + Options.get("MakeSmartPtrFunctionHeader", StdMemoryHeader)), + MakeSmartPtrFunctionName( + Options.get("MakeSmartPtrFunction", MakeSmartPtrFunctionName)), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void MakeSmartPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", IncludeStyle); + Options.store(Opts, "MakeSmartPtrFunctionHeader", MakeSmartPtrFunctionHeader); + Options.store(Opts, "MakeSmartPtrFunction", MakeSmartPtrFunctionName); + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +bool MakeSmartPtrCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus11; +} + +void MakeSmartPtrCheck::registerPPCallbacks(CompilerInstance &Compiler) { + if (isLanguageVersionSupported(getLangOpts())) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); + } +} + +void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + if (!isLanguageVersionSupported(getLangOpts())) + 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)), + unless(isInTemplateInstantiation())) + .bind(ConstructorCall)))), + this); + + Finder->addMatcher( + cxxMemberCallExpr( + thisPointerType(getSmartPointerTypeMatcher()), + callee(cxxMethodDecl(hasName("reset"))), + hasArgument(0, cxxNewExpr(CanCallCtor).bind(NewExpression)), + unless(isInTemplateInstantiation())) + .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 InMacro = ConstructCallStart.isMacroID(); + + if (InMacro && IgnoreMacros) { + return; + } + + 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; + + // Disable the fix in macros. + if (InMacro) { + return; + } + + if (!replaceNew(Diag, New, SM)) { + return; + } + + // 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)), + ")"); + } + + 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()); + + bool InMacro = ExprStart.isMacroID(); + + if (InMacro && IgnoreMacros) { + return; + } + + // There are some cases where we don't have operator ("." or "->") of the + // "reset" expression, e.g. call "reset()" method directly in the subclass of + // "std::unique_ptr<>". We skip these cases. + if (OperatorLoc.isInvalid()) { + return; + } + + auto Diag = diag(ResetCallStart, "use %0 instead") + << MakeSmartPtrFunctionName; + + // Disable the fix in macros. + if (InMacro) { + return; + } + + if (!replaceNew(Diag, New, SM)) { + return; + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OperatorLoc, ExprEnd), + (llvm::Twine(" = ") + MakeSmartPtrFunctionName + "<" + + GetNewExprName(New, SM, getLangOpts()) + ">") + .str()); + + if (Expr->isArrow()) + Diag << FixItHint::CreateInsertion(ExprStart, "*"); + + insertHeader(Diag, SM.getFileID(OperatorLoc)); +} + +bool MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, + const CXXNewExpr *New, + SourceManager& SM) { + SourceLocation NewStart = New->getSourceRange().getBegin(); + SourceLocation NewEnd = New->getSourceRange().getEnd(); + + // Skip when the source location of the new expression is invalid. + if (NewStart.isInvalid() || NewEnd.isInvalid()) + return false; + + 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: { + // FIXME: Add fixes for constructors with parameters that can be created + // with a C++11 braced-init-list (e.g. std::vector, std::map). + // Unlike ordinal cases, braced list can not be deduced in + // std::make_smart_ptr, we need to specify the type explicitly in the fixes: + // struct S { S(std::initializer_list, int); }; + // struct S2 { S2(std::vector); }; + // smart_ptr(new S({1, 2, 3}, 1)); // C++98 call-style initialization + // smart_ptr(new S({}, 1)); + // smart_ptr(new S2({1})); // implicit conversion: + // // std::initializer_list => std::vector + // The above samples have to be replaced with: + // std::make_smart_ptr(std::initializer_list({1, 2, 3}), 1); + // std::make_smart_ptr(std::initializer_list({}), 1); + // std::make_smart_ptr(std::vector({1})); + if (const auto *CE = New->getConstructExpr()) { + for (const auto *Arg : CE->arguments()) { + if (isa(Arg)) { + return false; + } + // Check whether we construct a class from a std::initializer_list. + // If so, we won't generate the fixes. + auto IsStdInitListInitConstructExpr = [](const Expr* E) { + assert(E); + if (const auto *ImplicitCE = dyn_cast(E)) { + if (ImplicitCE->isStdInitListInitialization()) + return true; + } + return false; + }; + if (IsStdInitListInitConstructExpr(Arg->IgnoreImplicit())) + return false; + } + } + 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()) { + if (NewConstruct->isStdInitListInitialization()) { + // FIXME: Add fixes for direct initialization with the initializer-list + // constructor. Similar to the above CallInit case, the type has to be + // specified explicitly in the fixes. + // struct S { S(std::initializer_list); }; + // smart_ptr(new S{1, 2, 3}); // C++11 direct list-initialization + // smart_ptr(new S{}); // use initializer-list consturctor + // The above cases have to be replaced with: + // std::make_smart_ptr(std::initializer_list({1, 2, 3})); + // std::make_smart_ptr(std::initializer_list({})); + return false; + } else { + // Direct initialization with ordinary constructors. + // struct S { S(int x); S(); }; + // smart_ptr(new S{5}); + // smart_ptr(new S{}); // use default constructor + // The arguments in the initialization list are going to be forwarded to + // the constructor, so this has to be replaced with: + // std::make_smart_ptr(5); + // std::make_smart_ptr(); + 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; + } + } + return true; +} + +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..6622482a2 --- /dev/null +++ b/clang-tidy/modernize/MakeSmartPtrCheck.h @@ -0,0 +1,73 @@ +//===--- 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; + + /// Returns whether the C++ version is compatible with current check. + virtual bool isLanguageVersionSupported(const LangOptions &LangOpts) const; + + 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; + const bool IgnoreMacros; + + void checkConstruct(SourceManager &SM, const CXXConstructExpr *Construct, + const QualType *Type, const CXXNewExpr *New); + void checkReset(SourceManager &SM, const CXXMemberCallExpr *Member, + const CXXNewExpr *New); + + /// Returns true when the fixes for replacing CXXNewExpr are generated. + bool 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..3ebbb071b --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.cpp @@ -0,0 +1,47 @@ +//===--- 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"), + RequireCPlusPlus14(Options.get("MakeSmartPtrFunction", "").empty()) {} + +MakeUniqueCheck::SmartPtrTypeMatcher +MakeUniqueCheck::getSmartPointerTypeMatcher() const { + return qualType(hasUnqualifiedDesugaredType( + recordType(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)))))))))))))))); +} + +bool MakeUniqueCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return RequireCPlusPlus14 ? LangOpts.CPlusPlus14 : LangOpts.CPlusPlus11; +} + +} // 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..587b41e0b --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.h @@ -0,0 +1,45 @@ +//===--- 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; + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + +private: + const bool RequireCPlusPlus14; +}; + +} // 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..63b7d0226 --- /dev/null +++ b/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -0,0 +1,116 @@ +//===--- 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 "UseUncaughtExceptionsCheck.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-uncaught-exceptions"); + 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..1ab73d50c --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.cpp @@ -0,0 +1,234 @@ +//===--- 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 { + +namespace { +/// \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; +} +} // namespace + +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.getLocalOrGlobal("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 performance-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..868c5c7cf --- /dev/null +++ b/clang-tidy/modernize/RawStringLiteralCheck.cpp @@ -0,0 +1,158 @@ +//===--- 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, + const CharsBitSet &DisallowedChars) { + // FIXME: Handle L"", u8"", u"" and U"" literals. + if (!Literal->isAscii()) + return false; + + for (const unsigned char C : Literal->getBytes()) + if (DisallowedChars.test(C)) + 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)) { + // Non-printing characters are disallowed: + // \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 + for (const unsigned char C : 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)) + DisallowedChars.set(C); + + // Non-ASCII are disallowed too. + for (unsigned int C = 0x80u; C <= 0xFFu; ++C) + DisallowedChars.set(static_cast(C)); +} + +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, DisallowedChars)) { + 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..f7721f6af --- /dev/null +++ b/clang-tidy/modernize/RawStringLiteralCheck.h @@ -0,0 +1,49 @@ +//===--- 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" +#include + +namespace clang { +namespace tidy { +namespace modernize { + +using CharsBitSet = std::bitset<1 << CHAR_BIT>; + +/// 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; + CharsBitSet DisallowedChars; + 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..04ecf2128 --- /dev/null +++ b/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -0,0 +1,202 @@ +//===--- 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 { + +namespace { +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")); +} + +} // namespace + +ReplaceAutoPtrCheck::ReplaceAutoPtrCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.getLocalOrGlobal("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..cd9aa11d1 --- /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.getLocalOrGlobal("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..f197399b0 --- /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(hasCanonicalType(hasDeclaration(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..67c00630a --- /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().CPlusPlus17) + 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..868716224 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.cpp @@ -0,0 +1,503 @@ +//===--- 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" +#include "clang/Basic/CharInfo.h" +#include "clang/Tooling/FixIt.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"; + +size_t GetTypeNameLength(bool RemoveStars, StringRef Text) { + enum CharType { Space, Alpha, Punctuation }; + CharType LastChar = Space, BeforeSpace = Punctuation; + size_t NumChars = 0; + int TemplateTypenameCntr = 0; + for (const unsigned char C : Text) { + if (C == '<') + ++TemplateTypenameCntr; + else if (C == '>') + --TemplateTypenameCntr; + const CharType NextChar = + isAlphanumeric(C) + ? Alpha + : (isWhitespace(C) || + (!RemoveStars && TemplateTypenameCntr == 0 && C == '*')) + ? Space + : Punctuation; + if (NextChar != Space) { + ++NumChars; // Count the non-space character. + if (LastChar == Space && NextChar == Alpha && BeforeSpace == Alpha) + ++NumChars; // Count a single space character between two words. + BeforeSpace = NextChar; + } + LastChar = NextChar; + } + return NumChars; +} + +/// \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), + MinTypeNameLength(Options.get("MinTypeNameLength", 5)), + RemoveStars(Options.get("RemoveStars", 0)) {} + +void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MinTypeNameLength", MinTypeNameLength); + 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()); + + if (MinTypeNameLength != 0 && + GetTypeNameLength(RemoveStars, + tooling::fixit::getText(Loc.getSourceRange(), + FirstDecl->getASTContext())) < + MinTypeNameLength) + return; + + 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..a061c5ffd --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.h @@ -0,0 +1,40 @@ +//===--- 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 unsigned int MinTypeNameLength; + 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..c14c6853b --- /dev/null +++ b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp @@ -0,0 +1,247 @@ +//===--- 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", true) != 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( + cxxCtorInitializer( + forField(unless(anyOf(getLangOpts().CPlusPlus2a + ? unless(anything()) + : isBitField(), + hasInClassInitializer(anything()), + hasParent(recordDecl(isUnion()))))), + isWritten(), withInitializer(ignoringImplicit(Init))) + .bind("default"))), + this); + + Finder->addMatcher( + cxxConstructorDecl( + unless(ast_matchers::isTemplateInstantiation()), + forEachConstructorInitializer( + cxxCtorInitializer(forField(hasInClassInitializer(anything())), + 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->getAnyMember(); + + 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..4d5d80146 --- /dev/null +++ b/clang-tidy/modernize/UseEmplaceCheck.cpp @@ -0,0 +1,177 @@ +//===--- 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), + IgnoreImplicitConstructors(Options.get("IgnoreImplicitConstructors", 0)), + 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 *CtorCall = Result.Nodes.getNodeAs("ctor"); + const auto *MakeCall = Result.Nodes.getNodeAs("make"); + assert((CtorCall || MakeCall) && "No push_back parameter matched"); + + if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 && + CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange()) + return; + + 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()) + : CtorCall->getParenOrBraceRange(); + + // Finish if there is no explicit constructor call. + if (CallParensRange.getBegin().isInvalid()) + return; + + const SourceLocation ExprBegin = + MakeCall ? MakeCall->getExprLoc() : CtorCall->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..2efb21299 --- /dev/null +++ b/clang-tidy/modernize/UseEmplaceCheck.h @@ -0,0 +1,47 @@ +//===--- 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: + const bool IgnoreImplicitConstructors; + const std::vector ContainersWithPushBack; + const std::vector SmartPointers; + const std::vector TupleTypes; + const 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..88f4485c1 --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp @@ -0,0 +1,313 @@ +//===--- 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, + initListExpr(has(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(); +} + +UseEqualsDefaultCheck::UseEqualsDefaultCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true) != 0) {} + +void UseEqualsDefaultCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // 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); + + if (IgnoreMacros && SpecialFunctionDecl->getLocation().isMacroID()) + return; + + // 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..a55c222d6 --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDefaultCheck.h @@ -0,0 +1,53 @@ +//===--- 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); + 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 IgnoreMacros; +}; + +} // 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..869381ab0 --- /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(); + 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..476c549e2 --- /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 ImmediateMacroArgLoc, MacroLoc; + // Skip NULL macros used in macro. + if (!getMacroAndArgLocations(StartLoc, ImmediateMacroArgLoc, MacroLoc) || + ImmediateMacroArgLoc != 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).getBegin(); + + 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).getBegin(); + 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/UseUncaughtExceptionsCheck.cpp b/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp new file mode 100644 index 000000000..9ac6e1d53 --- /dev/null +++ b/clang-tidy/modernize/UseUncaughtExceptionsCheck.cpp @@ -0,0 +1,104 @@ +//===--- UseUncaughtExceptionsCheck.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 "UseUncaughtExceptionsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void UseUncaughtExceptionsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus17) + return; + + std::string MatchText = "::std::uncaught_exception"; + + // Using declaration: warning and fix-it. + Finder->addMatcher( + usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(hasName(MatchText)))) + .bind("using_decl"), + this); + + // DeclRefExpr: warning, no fix-it. + Finder->addMatcher(declRefExpr(allOf(to(functionDecl(hasName(MatchText))), + unless(callExpr()))) + .bind("decl_ref_expr"), + this); + + // CallExpr: warning, fix-it. + Finder->addMatcher( + callExpr(allOf(hasDeclaration(functionDecl(hasName(MatchText))), + unless(hasAncestor(initListExpr())))) + .bind("call_expr"), + this); + // CallExpr in initialisation list: warning, fix-it with avoiding narrowing + // conversions. + Finder->addMatcher( + callExpr(allOf(hasAncestor(initListExpr()), + hasDeclaration(functionDecl(hasName(MatchText))))) + .bind("init_call_expr"), + this); +} + +void UseUncaughtExceptionsCheck::check(const MatchFinder::MatchResult &Result) { + SourceLocation BeginLoc; + SourceLocation EndLoc; + const CallExpr *C = Result.Nodes.getNodeAs("init_call_expr"); + bool WarnOnly = false; + + if (C) { + BeginLoc = C->getLocStart(); + EndLoc = C->getLocEnd(); + } else if (const auto *E = Result.Nodes.getNodeAs("call_expr")) { + BeginLoc = E->getLocStart(); + EndLoc = E->getLocEnd(); + } else if (const auto *D = + Result.Nodes.getNodeAs("decl_ref_expr")) { + BeginLoc = D->getLocStart(); + EndLoc = D->getLocEnd(); + WarnOnly = true; + } else { + const auto *U = Result.Nodes.getNodeAs("using_decl"); + assert(U && "Null pointer, no node provided"); + BeginLoc = U->getNameInfo().getBeginLoc(); + EndLoc = U->getNameInfo().getEndLoc(); + } + + auto Diag = diag(BeginLoc, "'std::uncaught_exception' is deprecated, use " + "'std::uncaught_exceptions' instead"); + + if (!BeginLoc.isMacroID()) { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + *Result.SourceManager, getLangOpts()); + + Text.consume_back("()"); + int TextLength = Text.size(); + + if (WarnOnly) { + return; + } + + if (!C) { + Diag << FixItHint::CreateInsertion(BeginLoc.getLocWithOffset(TextLength), + "s"); + } else { + Diag << FixItHint::CreateReplacement(C->getSourceRange(), + "std::uncaught_exceptions() > 0"); + } + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseUncaughtExceptionsCheck.h b/clang-tidy/modernize/UseUncaughtExceptionsCheck.h new file mode 100644 index 000000000..2b9660c76 --- /dev/null +++ b/clang-tidy/modernize/UseUncaughtExceptionsCheck.h @@ -0,0 +1,37 @@ +//===--- UseUncaughtExceptionsCheck.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_UNCAUGHT_EXCEPTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_UNCAUGHT_EXCEPTIONS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check will warn on calls to std::uncaught_exception and replace them with calls to +/// std::uncaught_exceptions, since std::uncaught_exception was deprecated in C++17. In case of +/// macro ID there will be only a warning without fixits. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-uncaught-exceptions.html +class UseUncaughtExceptionsCheck : public ClangTidyCheck { +public: + UseUncaughtExceptionsCheck(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_UNCAUGHT_EXCEPTIONS_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/objc/AvoidNSErrorInitCheck.cpp b/clang-tidy/objc/AvoidNSErrorInitCheck.cpp new file mode 100644 index 000000000..86c4656c1 --- /dev/null +++ b/clang-tidy/objc/AvoidNSErrorInitCheck.cpp @@ -0,0 +1,41 @@ +//===--- AvoidNSErrorInitCheck.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 "AvoidNSErrorInitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace objc { + +void AvoidNSErrorInitCheck::registerMatchers(MatchFinder *Finder) { + // this check should only be applied to ObjC sources. + if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) { + return; + } + Finder->addMatcher(objcMessageExpr(hasSelector("init"), + hasReceiverType(asString("NSError *"))) + .bind("nserrorInit"), + this); +} + +void AvoidNSErrorInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedExpr = + Result.Nodes.getNodeAs("nserrorInit"); + diag(MatchedExpr->getLocStart(), + "use errorWithDomain:code:userInfo: or initWithDomain:code:userInfo: to " + "create a new NSError"); +} + +} // namespace objc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/objc/AvoidNSErrorInitCheck.h b/clang-tidy/objc/AvoidNSErrorInitCheck.h new file mode 100644 index 000000000..bec19b289 --- /dev/null +++ b/clang-tidy/objc/AvoidNSErrorInitCheck.h @@ -0,0 +1,36 @@ +//===--- AvoidNSErrorInitCheck.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_OBJC_AVOIDNSERRORINITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_AVOIDNSERRORINITCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace objc { + +/// Finds usages of [NSSError init]. It is not the proper way of creating +/// NSError. errorWithDomain:code:userInfo: should be used instead. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/objc-avoid-nserror-init.html +class AvoidNSErrorInitCheck : public ClangTidyCheck { + public: + AvoidNSErrorInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace objc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_AVOIDNSERRORINITCHECK_H diff --git a/clang-tidy/objc/AvoidSpinlockCheck.cpp b/clang-tidy/objc/AvoidSpinlockCheck.cpp new file mode 100644 index 000000000..21ec36421 --- /dev/null +++ b/clang-tidy/objc/AvoidSpinlockCheck.cpp @@ -0,0 +1,37 @@ +//===--- AvoidSpinlockCheck.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 "AvoidSpinlockCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace objc { + +void AvoidSpinlockCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(callee((functionDecl(hasAnyName( + "OSSpinlockLock", "OSSpinlockUnlock", "OSSpinlockTry"))))) + .bind("spinlock"), + this); +} + +void AvoidSpinlockCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedExpr = Result.Nodes.getNodeAs("spinlock"); + diag(MatchedExpr->getLocStart(), + "use os_unfair_lock_lock() or dispatch queue APIs instead of the " + "deprecated OSSpinLock"); +} + +} // namespace objc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/objc/AvoidSpinlockCheck.h b/clang-tidy/objc/AvoidSpinlockCheck.h new file mode 100644 index 000000000..d9dbf8cb7 --- /dev/null +++ b/clang-tidy/objc/AvoidSpinlockCheck.h @@ -0,0 +1,36 @@ +//===--- AvoidSpinlockCheck.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_OBJC_AVOID_SPINLOCK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_AVOID_SPINLOCK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace objc { + +/// Finds usages of OSSpinlock, which is deprecated due to potential livelock +/// problems. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/objc-avoid-spinlock.html +class AvoidSpinlockCheck : public ClangTidyCheck { + public: + AvoidSpinlockCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace objc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_AVOID_SPINLOCK_H diff --git a/clang-tidy/objc/CMakeLists.txt b/clang-tidy/objc/CMakeLists.txt new file mode 100644 index 000000000..4063bba11 --- /dev/null +++ b/clang-tidy/objc/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyObjCModule + AvoidNSErrorInitCheck.cpp + AvoidSpinlockCheck.cpp + ForbiddenSubclassingCheck.cpp + ObjCTidyModule.cpp + PropertyDeclarationCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/objc/ForbiddenSubclassingCheck.cpp b/clang-tidy/objc/ForbiddenSubclassingCheck.cpp new file mode 100644 index 000000000..e78cb995d --- /dev/null +++ b/clang-tidy/objc/ForbiddenSubclassingCheck.cpp @@ -0,0 +1,122 @@ +//===--- ForbiddenSubclassingCheck.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 "ForbiddenSubclassingCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/SmallVector.h" +#include "../utils/OptionsUtils.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace objc { + +namespace { + +constexpr char DefaultForbiddenSuperClassNames[] = + "ABNewPersonViewController;" + "ABPeoplePickerNavigationController;" + "ABPersonViewController;" + "ABUnknownPersonViewController;" + "NSHashTable;" + "NSMapTable;" + "NSPointerArray;" + "NSPointerFunctions;" + "NSTimer;" + "UIActionSheet;" + "UIAlertView;" + "UIImagePickerController;" + "UITextInputMode;" + "UIWebView"; + +/// \brief Matches Objective-C classes that directly or indirectly +/// have a superclass matching \c Base. +/// +/// Note that a class is not considered to be a subclass of itself. +/// +/// Example matches Y, Z +/// (matcher = objcInterfaceDecl(hasName("X"))) +/// \code +/// @interface X +/// @end +/// @interface Y : X // directly derived +/// @end +/// @interface Z : Y // indirectly derived +/// @end +/// \endcode +AST_MATCHER_P(ObjCInterfaceDecl, isSubclassOf, + ast_matchers::internal::Matcher, Base) { + for (const auto *SuperClass = Node.getSuperClass(); + SuperClass != nullptr; + SuperClass = SuperClass->getSuperClass()) { + if (Base.matches(*SuperClass, Finder, Builder)) { + return true; + } + } + return false; +} + +} // namespace + +ForbiddenSubclassingCheck::ForbiddenSubclassingCheck( + StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ForbiddenSuperClassNames( + utils::options::parseStringList( + Options.get("ClassNames", DefaultForbiddenSuperClassNames))) { +} + +void ForbiddenSubclassingCheck::registerMatchers(MatchFinder *Finder) { + // this check should only be applied to ObjC sources. + if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) { + return; + } + Finder->addMatcher( + objcInterfaceDecl( + isSubclassOf( + objcInterfaceDecl( + hasAnyName( + std::vector( + ForbiddenSuperClassNames.begin(), + ForbiddenSuperClassNames.end()))) + .bind("superclass"))) + .bind("subclass"), + this); +} + +void ForbiddenSubclassingCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *SubClass = Result.Nodes.getNodeAs( + "subclass"); + assert(SubClass != nullptr); + const auto *SuperClass = Result.Nodes.getNodeAs( + "superclass"); + assert(SuperClass != nullptr); + diag(SubClass->getLocation(), + "Objective-C interface %0 subclasses %1, which is not " + "intended to be subclassed") + << SubClass + << SuperClass; +} + +void ForbiddenSubclassingCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store( + Opts, + "ForbiddenSuperClassNames", + utils::options::serializeStringList(ForbiddenSuperClassNames)); +} + +} // namespace objc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/objc/ForbiddenSubclassingCheck.h b/clang-tidy/objc/ForbiddenSubclassingCheck.h new file mode 100644 index 000000000..6c7e08b33 --- /dev/null +++ b/clang-tidy/objc/ForbiddenSubclassingCheck.h @@ -0,0 +1,42 @@ +//===--- ForbiddenSubclassingCheck.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_OBJC_FORBIDDEN_SUBCLASSING_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_FORBIDDEN_SUBCLASSING_CHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { +namespace tidy { +namespace objc { + +/// Finds Objective-C classes which have a superclass which is +/// documented to not support subclassing. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/objc-forbidden-subclassing.html +class ForbiddenSubclassingCheck : public ClangTidyCheck { +public: + ForbiddenSubclassingCheck(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::vector ForbiddenSuperClassNames; +}; + +} // namespace objc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_FORBIDDEN_SUBCLASSING_CHECK_H diff --git a/clang-tidy/objc/ObjCTidyModule.cpp b/clang-tidy/objc/ObjCTidyModule.cpp new file mode 100644 index 000000000..19152c21b --- /dev/null +++ b/clang-tidy/objc/ObjCTidyModule.cpp @@ -0,0 +1,50 @@ +//===--- ObjCTidyModule.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 "AvoidNSErrorInitCheck.h" +#include "AvoidSpinlockCheck.h" +#include "ForbiddenSubclassingCheck.h" +#include "PropertyDeclarationCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace objc { + +class ObjCModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "objc-avoid-nserror-init"); + CheckFactories.registerCheck( + "objc-avoid-spinlock"); + CheckFactories.registerCheck( + "objc-forbidden-subclassing"); + CheckFactories.registerCheck( + "objc-property-declaration"); + } +}; + +// Register the ObjCTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X( + "objc-module", + "Adds Objective-C lint checks."); + +} // namespace objc + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ObjCModule. +volatile int ObjCModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/objc/PropertyDeclarationCheck.cpp b/clang-tidy/objc/PropertyDeclarationCheck.cpp new file mode 100644 index 000000000..d7715fa2c --- /dev/null +++ b/clang-tidy/objc/PropertyDeclarationCheck.cpp @@ -0,0 +1,263 @@ +//===--- PropertyDeclarationCheck.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 "PropertyDeclarationCheck.h" +#include +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Regex.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace objc { + +namespace { + +// For StandardProperty the naming style is 'lowerCamelCase'. +// For CategoryProperty especially in categories of system class, +// to avoid naming conflict, the suggested naming style is +// 'abc_lowerCamelCase' (adding lowercase prefix followed by '_'). +enum NamingStyle { + StandardProperty = 1, + CategoryProperty = 2, +}; + +/// The acronyms are aggregated from multiple sources including +/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE +/// +/// Keep this list sorted. +constexpr llvm::StringLiteral DefaultSpecialAcronyms[] = { + "[2-9]G", + "ACL", + "API", + "AR", + "ARGB", + "ASCII", + "AV", + "BGRA", + "CA", + "CF", + "CG", + "CI", + "CRC", + "CV", + "CMYK", + "DNS", + "FPS", + "FTP", + "GIF", + "GL", + "GPS", + "GUID", + "HD", + "HDR", + "HMAC", + "HTML", + "HTTP", + "HTTPS", + "HUD", + "ID", + "JPG", + "JS", + "LAN", + "LZW", + "MAC", + "MD", + "MDNS", + "MIDI", + "NS", + "OS", + "PDF", + "PIN", + "PNG", + "POI", + "PSTN", + "PTR", + "QA", + "QOS", + "RGB", + "RGBA", + "RGBX", + "RIPEMD", + "ROM", + "RPC", + "RTF", + "RTL", + "SC", + "SDK", + "SHA", + "SQL", + "SSO", + "TCP", + "TIFF", + "TTS", + "UI", + "URI", + "URL", + "UUID", + "VC", + "VOIP", + "VPN", + "VR", + "W", + "WAN", + "X", + "XML", + "Y", + "Z", +}; + +/// For now we will only fix 'CamelCase' or 'abc_CamelCase' property to +/// 'camelCase' or 'abc_camelCase'. For other cases the users need to +/// come up with a proper name by their own. +/// FIXME: provide fix for snake_case to snakeCase +FixItHint generateFixItHint(const ObjCPropertyDecl *Decl, NamingStyle Style) { + auto Name = Decl->getName(); + auto NewName = Decl->getName().str(); + size_t Index = 0; + if (Style == CategoryProperty) { + Index = Name.find_first_of('_') + 1; + NewName.replace(0, Index - 1, Name.substr(0, Index - 1).lower()); + } + if (Index < Name.size()) { + NewName[Index] = tolower(NewName[Index]); + if (NewName != Name) { + return FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())), + llvm::StringRef(NewName)); + } + } + return FixItHint(); +} + +std::string AcronymsGroupRegex(llvm::ArrayRef EscapedAcronyms) { + return "(" + + llvm::join(EscapedAcronyms.begin(), EscapedAcronyms.end(), "s?|") + + "s?)"; +} + +std::string validPropertyNameRegex(llvm::ArrayRef EscapedAcronyms, + bool UsedInMatcher) { + // Allow any of these names: + // foo + // fooBar + // url + // urlString + // URL + // URLString + // bundleID + std::string StartMatcher = UsedInMatcher ? "::" : "^"; + std::string AcronymsMatcher = AcronymsGroupRegex(EscapedAcronyms); + return StartMatcher + "(" + AcronymsMatcher + "[A-Z]?)?[a-z]+[a-z0-9]*(" + + AcronymsMatcher + "|([A-Z][a-z0-9]+)|A|I)*$"; +} + +bool hasCategoryPropertyPrefix(llvm::StringRef PropertyName) { + auto RegexExp = llvm::Regex("^[a-zA-Z]+_[a-zA-Z0-9][a-zA-Z0-9_]+$"); + return RegexExp.match(PropertyName); +} + +bool prefixedPropertyNameValid(llvm::StringRef PropertyName, + llvm::ArrayRef Acronyms) { + size_t Start = PropertyName.find_first_of('_'); + assert(Start != llvm::StringRef::npos && Start + 1 < PropertyName.size()); + auto Prefix = PropertyName.substr(0, Start); + if (Prefix.lower() != Prefix) { + return false; + } + auto RegexExp = + llvm::Regex(llvm::StringRef(validPropertyNameRegex(Acronyms, false))); + return RegexExp.match(PropertyName.substr(Start + 1)); +} +} // namespace + +PropertyDeclarationCheck::PropertyDeclarationCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SpecialAcronyms( + utils::options::parseStringList(Options.get("Acronyms", ""))), + IncludeDefaultAcronyms(Options.get("IncludeDefaultAcronyms", true)), + EscapedAcronyms() {} + +void PropertyDeclarationCheck::registerMatchers(MatchFinder *Finder) { + // this check should only be applied to ObjC sources. + if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) { + return; + } + if (IncludeDefaultAcronyms) { + EscapedAcronyms.reserve(llvm::array_lengthof(DefaultSpecialAcronyms) + + SpecialAcronyms.size()); + // No need to regex-escape the default acronyms. + EscapedAcronyms.insert(EscapedAcronyms.end(), + std::begin(DefaultSpecialAcronyms), + std::end(DefaultSpecialAcronyms)); + } else { + EscapedAcronyms.reserve(SpecialAcronyms.size()); + } + // In case someone defines a prefix which includes a regex + // special character, regex-escape all the user-defined prefixes. + std::transform(SpecialAcronyms.begin(), SpecialAcronyms.end(), + std::back_inserter(EscapedAcronyms), + [](const std::string &s) { return llvm::Regex::escape(s); }); + Finder->addMatcher( + objcPropertyDecl( + // the property name should be in Lower Camel Case like + // 'lowerCamelCase' + unless(matchesName(validPropertyNameRegex(EscapedAcronyms, true)))) + .bind("property"), + this); +} + +void PropertyDeclarationCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = + Result.Nodes.getNodeAs("property"); + assert(MatchedDecl->getName().size() > 0); + auto *DeclContext = MatchedDecl->getDeclContext(); + auto *CategoryDecl = llvm::dyn_cast(DeclContext); + + auto AcronymsRegex = + llvm::Regex("^" + AcronymsGroupRegex(EscapedAcronyms) + "$"); + if (AcronymsRegex.match(MatchedDecl->getName())) { + return; + } + if (CategoryDecl != nullptr && + hasCategoryPropertyPrefix(MatchedDecl->getName())) { + if (!prefixedPropertyNameValid(MatchedDecl->getName(), EscapedAcronyms) || + CategoryDecl->IsClassExtension()) { + NamingStyle Style = CategoryDecl->IsClassExtension() ? StandardProperty + : CategoryProperty; + diag(MatchedDecl->getLocation(), + "property name '%0' not using lowerCamelCase style or not prefixed " + "in a category, according to the Apple Coding Guidelines") + << MatchedDecl->getName() << generateFixItHint(MatchedDecl, Style); + } + return; + } + diag(MatchedDecl->getLocation(), + "property name '%0' not using lowerCamelCase style or not prefixed in " + "a category, according to the Apple Coding Guidelines") + << MatchedDecl->getName() + << generateFixItHint(MatchedDecl, StandardProperty); +} + +void PropertyDeclarationCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Acronyms", + utils::options::serializeStringList(SpecialAcronyms)); + Options.store(Opts, "IncludeDefaultAcronyms", IncludeDefaultAcronyms); +} + +} // namespace objc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/objc/PropertyDeclarationCheck.h b/clang-tidy/objc/PropertyDeclarationCheck.h new file mode 100644 index 000000000..b2683baed --- /dev/null +++ b/clang-tidy/objc/PropertyDeclarationCheck.h @@ -0,0 +1,46 @@ +//===--- PropertyDeclarationCheck.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_OBJC_PROPERTY_DECLARATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_PROPERTY_DECLARATION_H + +#include "../ClangTidy.h" +#include +#include + +namespace clang { +namespace tidy { +namespace objc { + +/// Finds Objective-C property declarations which +/// are not in Lower Camel Case. +/// +/// The format of property should look like: +/// @property(nonatomic) NSString *lowerCamelCase; +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/objc-property-declaration.html +class PropertyDeclarationCheck : public ClangTidyCheck { +public: + PropertyDeclarationCheck(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::vector SpecialAcronyms; + const bool IncludeDefaultAcronyms; + std::vector EscapedAcronyms; +}; + +} // namespace objc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OBJC_PROPERTY_DECLARATION_H diff --git a/clang-tidy/performance/CMakeLists.txt b/clang-tidy/performance/CMakeLists.txt new file mode 100644 index 000000000..ac417bcc8 --- /dev/null +++ b/clang-tidy/performance/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyPerformanceModule + FasterStringFindCheck.cpp + ForRangeCopyCheck.cpp + ImplicitConversionInLoopCheck.cpp + InefficientAlgorithmCheck.cpp + InefficientStringConcatenationCheck.cpp + InefficientVectorOperationCheck.cpp + MoveConstArgCheck.cpp + MoveConstructorInitCheck.cpp + NoexceptMoveConstructorCheck.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..eddc52b63 --- /dev/null +++ b/clang-tidy/performance/FasterStringFindCheck.cpp @@ -0,0 +1,104 @@ +//===--- 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(hasUnqualifiedDesugaredType(recordType(hasDeclaration( + 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..2358aacb2 --- /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/ExprMutationAnalyzer.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::ExprMutationAnalyzer(ForRange.getBody(), &Context) + .isMutated(&LoopVar)) + 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/ImplicitConversionInLoopCheck.cpp b/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp new file mode 100644 index 000000000..1a5605f5f --- /dev/null +++ b/clang-tidy/performance/ImplicitConversionInLoopCheck.cpp @@ -0,0 +1,104 @@ +//===--- ImplicitConversionInLoopCheck.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 "ImplicitConversionInLoopCheck.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 { + +// 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. +static bool IsNonTrivialImplicitCast(const Stmt *ST) { + if (const auto *ICE = dyn_cast(ST)) { + return (ICE->getCastKind() != CK_NoOp) || + IsNonTrivialImplicitCast(ICE->getSubExpr()); + } + return false; +} + +void ImplicitConversionInLoopCheck::registerMatchers(MatchFinder *Finder) { + // We look for const ref loop variables that (optionally inside an + // ExprWithCleanup) materialize a temporary, and contain a implicit + // conversion. The check on the implicit conversion is done in check() because + // we can't access implicit conversion 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. We use both cxxOperatorCallExpr for user + // defined operator and unaryOperator when the iterator is a pointer, like + // for arrays or std::array. + // + // Note that when the implicit conversion is done through a user defined + // conversion 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(anyOf(hasDescendant( + cxxOperatorCallExpr().bind("operator-call")), + hasDescendant(unaryOperator(hasOperatorName("*")) + .bind("operator-call")))) + .bind("init"))) + .bind("faulty-var"))), + this); +} + +void ImplicitConversionInLoopCheck::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 ImplicitConversionInLoopCheck::ReportAndFix( + const ASTContext *Context, const VarDecl *VD, + const Expr *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 conversion; you can either " + "change the type to the matching 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/ImplicitConversionInLoopCheck.h b/clang-tidy/performance/ImplicitConversionInLoopCheck.h new file mode 100644 index 000000000..55cb84c3d --- /dev/null +++ b/clang-tidy/performance/ImplicitConversionInLoopCheck.h @@ -0,0 +1,38 @@ +//===--- ImplicitConversionInLoopCheck.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_CONVERSION_IN_LOOP_CHECK_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CONVERSION_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 ImplicitConversionInLoopCheck : public ClangTidyCheck { +public: + ImplicitConversionInLoopCheck(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 Expr *OperatorCall); +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CONVERSION_IN_LOOP_CHECK_H_ diff --git a/clang-tidy/performance/InefficientAlgorithmCheck.cpp b/clang-tidy/performance/InefficientAlgorithmCheck.cpp new file mode 100644 index 000000000..4471d0776 --- /dev/null +++ b/clang-tidy/performance/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 performance { + +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 performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/InefficientAlgorithmCheck.h b/clang-tidy/performance/InefficientAlgorithmCheck.h new file mode 100644 index 000000000..72506cf72 --- /dev/null +++ b/clang-tidy/performance/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_PERFORMANCE_INEFFICIENTALGORITHMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTALGORITHMCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// 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 performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTALGORITHMCHECK_H diff --git a/clang-tidy/performance/InefficientStringConcatenationCheck.cpp b/clang-tidy/performance/InefficientStringConcatenationCheck.cpp new file mode 100644 index 000000000..a17916d99 --- /dev/null +++ b/clang-tidy/performance/InefficientStringConcatenationCheck.cpp @@ -0,0 +1,88 @@ +//===--- 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(qualType(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(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/MoveConstArgCheck.cpp b/clang-tidy/performance/MoveConstArgCheck.cpp new file mode 100644 index 000000000..8d4948029 --- /dev/null +++ b/clang-tidy/performance/MoveConstArgCheck.cpp @@ -0,0 +1,121 @@ +//===--- MoveConstArgCheck.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 "MoveConstArgCheck.h" + +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +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 MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckTriviallyCopyableMove", CheckTriviallyCopyableMove); +} + +void MoveConstArgCheck::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 MoveConstArgCheck::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; + } + } + + if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove) + 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 performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/MoveConstArgCheck.h b/clang-tidy/performance/MoveConstArgCheck.h new file mode 100644 index 000000000..13ed9aeee --- /dev/null +++ b/clang-tidy/performance/MoveConstArgCheck.h @@ -0,0 +1,43 @@ +//===--- MoveConstArgCheck.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 performance { + +/// Find casts of calculation results to bigger type. Typically from int to +/// +/// There is one option: +/// +/// - `CheckTriviallyCopyableMove`: Whether to check for trivially-copyable +// types as their objects are not moved but copied. Enabled by default. +class MoveConstArgCheck : public ClangTidyCheck { +public: + MoveConstArgCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckTriviallyCopyableMove( + Options.get("CheckTriviallyCopyableMove", true)) {} + 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 CheckTriviallyCopyableMove; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H diff --git a/clang-tidy/performance/MoveConstructorInitCheck.cpp b/clang-tidy/performance/MoveConstructorInitCheck.cpp new file mode 100644 index 000000000..055e4f0a1 --- /dev/null +++ b/clang-tidy/performance/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 performance { + +MoveConstructorInitCheck::MoveConstructorInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.getLocalOrGlobal("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 performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/MoveConstructorInitCheck.h b/clang-tidy/performance/MoveConstructorInitCheck.h new file mode 100644 index 000000000..3b7dda06c --- /dev/null +++ b/clang-tidy/performance/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_PERFORMANCE_MOVECONSTRUCTORINITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_MOVECONSTRUCTORINITCHECK_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { +namespace performance { + +/// 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/performance-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 performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_MOVECONSTRUCTORINITCHECK_H diff --git a/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp b/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp new file mode 100644 index 000000000..111f24d8e --- /dev/null +++ b/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp @@ -0,0 +1,72 @@ +//===--- 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 performance { + +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; + + if (!isNoexceptExceptionSpec(ProtoType->getExceptionSpecType())) { + diag(Decl->getLocation(), "move %0s should be marked noexcept") + << MethodType; + // FIXME: Add a fixit. + return; + } + + // Don't complain about nothrow(false), but complain on nothrow(expr) + // where expr evaluates to false. + if (ProtoType->canThrow() == CT_Can) { + Expr *E = ProtoType->getNoexceptExpr(); + if (!isa(ProtoType->getNoexceptExpr())) { + diag(E->getExprLoc(), + "noexcept specifier on the move %0 evaluates to 'false'") + << MethodType; + } + } + } +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/NoexceptMoveConstructorCheck.h b/clang-tidy/performance/NoexceptMoveConstructorCheck.h new file mode 100644 index 000000000..9687ab1f0 --- /dev/null +++ b/clang-tidy/performance/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_PERFORMANCE_NOEXCEPTMOVECONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_NOEXCEPTMOVECONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// 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 performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_NOEXCEPTMOVECONSTRUCTORCHECK_H diff --git a/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tidy/performance/PerformanceTidyModule.cpp new file mode 100644 index 000000000..646c65956 --- /dev/null +++ b/clang-tidy/performance/PerformanceTidyModule.cpp @@ -0,0 +1,71 @@ +//===--- 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 "ImplicitConversionInLoopCheck.h" +#include "InefficientAlgorithmCheck.h" +#include "InefficientStringConcatenationCheck.h" +#include "InefficientVectorOperationCheck.h" +#include "MoveConstArgCheck.h" +#include "MoveConstructorInitCheck.h" +#include "NoexceptMoveConstructorCheck.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-conversion-in-loop"); + CheckFactories.registerCheck( + "performance-inefficient-algorithm"); + CheckFactories.registerCheck( + "performance-inefficient-string-concatenation"); + CheckFactories.registerCheck( + "performance-inefficient-vector-operation"); + CheckFactories.registerCheck( + "performance-move-const-arg"); + CheckFactories.registerCheck( + "performance-move-constructor-init"); + CheckFactories.registerCheck( + "performance-noexcept-move-constructor"); + 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..441bad38c --- /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.getLocalOrGlobal("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..e277ad341 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -0,0 +1,207 @@ +//===--- 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(); +} + +bool isExplicitTemplateSpecialization(const FunctionDecl &Function) { + if (const auto *SpecializationInfo = Function.getTemplateSpecializationInfo()) + if (SpecializationInfo->getTemplateSpecializationKind() == + TSK_ExplicitSpecialization) + return true; + if (const auto *Method = llvm::dyn_cast(&Function)) + if (Method->getTemplatedKind() == FunctionDecl::TK_MemberSpecialization && + Method->getMemberSpecializationInfo()->isExplicitSpecialization()) + return true; + return false; +} + +} // namespace + +UnnecessaryValueParamCheck::UnnecessaryValueParamCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.getLocalOrGlobal("IncludeStyle", "llvm"))) {} + +void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) { + // This check is specific to C++ and doesn't apply to languages like + // Objective-C. + if (!getLangOpts().CPlusPlus) + return; + 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. + // 4. the function is an explicit template specialization. + const auto *Method = llvm::dyn_cast(Function); + if (Param->getLocStart().isMacroID() || (Method && Method->isVirtual()) || + isReferencedOutsideOfCallExpr(*Function, *Result.Context) || + isExplicitTemplateSpecialization(*Function)) + 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..3540b2be7 --- /dev/null +++ b/clang-tidy/plugin/CMakeLists.txt @@ -0,0 +1,30 @@ +add_clang_library(clangTidyPlugin + ClangTidyPlugin.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangSema + clangTidy + clangTidyAbseilModule + clangTidyAndroidModule + clangTidyBoostModule + clangTidyBugproneModule + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyFuchsiaModule + clangTidyGoogleModule + clangTidyHICPPModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyMPIModule + clangTidyObjCModule + clangTidyPerformanceModule + clangTidyPortabilityModule + clangTidyReadabilityModule + clangTidyZirconModule + clangTooling + ) diff --git a/clang-tidy/plugin/ClangTidyPlugin.cpp b/clang-tidy/plugin/ClangTidyPlugin.cpp new file mode 100644 index 000000000..345561205 --- /dev/null +++ b/clang-tidy/plugin/ClangTidyPlugin.cpp @@ -0,0 +1,167 @@ +//===- 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 AbseilModule. +extern volatile int AbseilModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination = + AbseilModuleAnchorSource; + +// This anchor is used to force the linker to link the AndroidModule. +extern volatile int AndroidModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED AndroidModuleAnchorDestination = + AndroidModuleAnchorSource; + +// This anchor is used to force the linker to link the BoostModule. +extern volatile int BoostModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination = + BoostModuleAnchorSource; + +// This anchor is used to force the linker to link the CERTModule. +extern volatile int CERTModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = + CERTModuleAnchorSource; + +// 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 FuchsiaModule. +extern volatile int FuchsiaModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED FuchsiaModuleAnchorDestination = + FuchsiaModuleAnchorSource; + +// This anchor is used to force the linker to link the GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// This anchor is used to force the linker to link the HICPPModule. +extern volatile int HICPPModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination = + HICPPModuleAnchorSource; + +// This anchor is used to force the linker to link the LLVMModule. +extern volatile int LLVMModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = + LLVMModuleAnchorSource; + +// 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 ObjCModule. +extern volatile int ObjCModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ObjCModuleAnchorDestination = + ObjCModuleAnchorSource; + +// This anchor is used to force the linker to link the PerformanceModule. +extern volatile int PerformanceModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination = + PerformanceModuleAnchorSource; + +// This anchor is used to force the linker to link the PortabilityModule. +extern volatile int PortabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED PortabilityModuleAnchorDestination = + PortabilityModuleAnchorSource; + +// This anchor is used to force the linker to link the ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +// This anchor is used to force the linker to link the ZirconModule. +extern volatile int ZirconModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ZirconModuleAnchorDestination = + ZirconModuleAnchorSource; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/portability/CMakeLists.txt b/clang-tidy/portability/CMakeLists.txt new file mode 100644 index 000000000..0420a18a2 --- /dev/null +++ b/clang-tidy/portability/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyPortabilityModule + PortabilityTidyModule.cpp + SIMDIntrinsicsCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/portability/PortabilityTidyModule.cpp b/clang-tidy/portability/PortabilityTidyModule.cpp new file mode 100644 index 000000000..013cbcfea --- /dev/null +++ b/clang-tidy/portability/PortabilityTidyModule.cpp @@ -0,0 +1,38 @@ +//===--- PortabilityTidyModule.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 "SIMDIntrinsicsCheck.h" + +namespace clang { +namespace tidy { +namespace portability { + +class PortabilityModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "portability-simd-intrinsics"); + } +}; + +// Register the PortabilityModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("portability-module", "Adds portability-related checks."); + +} // namespace portability + +// This anchor is used to force the linker to link in the generated object file +// and thus register the PortabilityModule. +volatile int PortabilityModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/portability/SIMDIntrinsicsCheck.cpp b/clang-tidy/portability/SIMDIntrinsicsCheck.cpp new file mode 100644 index 000000000..d5434cc2e --- /dev/null +++ b/clang-tidy/portability/SIMDIntrinsicsCheck.cpp @@ -0,0 +1,160 @@ +//===--- SIMDIntrinsicsCheck.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 "SIMDIntrinsicsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Support/Regex.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace portability { + +namespace { + +// If the callee has parameter of VectorType or pointer to VectorType, +// or the return type is VectorType, we consider it a vector function +// and a candidate for checking. +AST_MATCHER(FunctionDecl, isVectorFunction) { + bool IsVector = Node.getReturnType()->isVectorType(); + for (const ParmVarDecl *Parm : Node.parameters()) { + QualType Type = Parm->getType(); + if (Type->isPointerType()) + Type = Type->getPointeeType(); + if (Type->isVectorType()) + IsVector = true; + } + return IsVector; +} + +} // namespace + +static StringRef TrySuggestPPC(StringRef Name) { + if (!Name.consume_front("vec_")) + return {}; + + static const llvm::StringMap Mapping{ + // [simd.alg] + {"max", "$std::max"}, + {"min", "$std::min"}, + + // [simd.binary] + {"add", "operator+ on $simd objects"}, + {"sub", "operator- on $simd objects"}, + {"mul", "operator* on $simd objects"}, + }; + + auto It = Mapping.find(Name); + if (It != Mapping.end()) + return It->second; + return {}; +} + +static StringRef TrySuggestX86(StringRef Name) { + if (!(Name.consume_front("_mm_") || Name.consume_front("_mm256_") || + Name.consume_front("_mm512_"))) + return {}; + + // [simd.alg] + if (Name.startswith("max_")) + return "$simd::max"; + if (Name.startswith("min_")) + return "$simd::min"; + + // [simd.binary] + if (Name.startswith("add_")) + return "operator+ on $simd objects"; + if (Name.startswith("sub_")) + return "operator- on $simd objects"; + if (Name.startswith("mul_")) + return "operator* on $simd objects"; + + return {}; +} + +SIMDIntrinsicsCheck::SIMDIntrinsicsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Std(Options.get("Std", "")), + Suggest(Options.get("Suggest", 0) != 0) {} + +void SIMDIntrinsicsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Std", ""); + Options.store(Opts, "Suggest", 0); +} + +void SIMDIntrinsicsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + // If Std is not specified, infer it from the language options. + // libcxx implementation backports it to C++11 std::experimental::simd. + if (Std.empty()) + Std = getLangOpts().CPlusPlus2a ? "std" : "std::experimental"; + + Finder->addMatcher(callExpr(callee(functionDecl(allOf( + matchesName("^::(_mm_|_mm256_|_mm512_|vec_)"), + isVectorFunction()))), + unless(isExpansionInSystemHeader())) + .bind("call"), + this); +} + +void SIMDIntrinsicsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Call != nullptr); + const FunctionDecl *Callee = Call->getDirectCallee(); + if (!Callee) + return; + + StringRef Old = Callee->getName(); + StringRef New; + llvm::Triple::ArchType Arch = + Result.Context->getTargetInfo().getTriple().getArch(); + + // We warn or suggest if this SIMD intrinsic function has a std::simd + // replacement. + switch (Arch) { + default: + break; + case llvm::Triple::ppc: + case llvm::Triple::ppc64: + case llvm::Triple::ppc64le: + New = TrySuggestPPC(Old); + break; + case llvm::Triple::x86: + case llvm::Triple::x86_64: + New = TrySuggestX86(Old); + break; + } + + // We have found a std::simd replacement. + if (!New.empty()) { + std::string Message; + // If Suggest is true, give a P0214 alternative, otherwise point it out it + // is non-portable. + if (Suggest) { + Message = (Twine("'") + Old + "' can be replaced by " + New).str(); + Message = llvm::Regex("\\$std").sub(Std, Message); + Message = + llvm::Regex("\\$simd").sub((Std.str() + "::simd").str(), Message); + } else { + Message = (Twine("'") + Old + "' is a non-portable " + + llvm::Triple::getArchTypeName(Arch) + " intrinsic function") + .str(); + } + diag(Call->getExprLoc(), Message); + } +} + +} // namespace portability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/portability/SIMDIntrinsicsCheck.h b/clang-tidy/portability/SIMDIntrinsicsCheck.h new file mode 100644 index 000000000..ebcc85545 --- /dev/null +++ b/clang-tidy/portability/SIMDIntrinsicsCheck.h @@ -0,0 +1,42 @@ +//===--- SIMDIntrinsicsCheck.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_SIMD_INTRINSICS_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/SmallString.h" + +namespace clang { +namespace tidy { +namespace portability { + +/// Find SIMD intrinsics calls and suggest std::experimental::simd alternatives. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/portability-simd-intrinsics.html +class SIMDIntrinsicsCheck : public ClangTidyCheck { +public: + SIMDIntrinsicsCheck(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: + llvm::SmallString<32> Std; + const bool Suggest; +}; + +} // namespace portability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_SIMD_INTRINSICS_CHECK_H 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..961ad88a3 --- /dev/null +++ b/clang-tidy/readability/CMakeLists.txt @@ -0,0 +1,42 @@ +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 + ImplicitBoolConversionCheck.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 + SimplifySubscriptExprCheck.cpp + StaticAccessedThroughInstanceCheck.cpp + StaticDefinitionInAnonymousNamespaceCheck.cpp + StringCompareCheck.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..5604354f1 --- /dev/null +++ b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -0,0 +1,225 @@ +//===--- 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 = qualType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(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..8b5526caf --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.cpp @@ -0,0 +1,57 @@ +//===--- 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"), + expr(ignoringImplicit(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..8ed6b9077 --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.cpp @@ -0,0 +1,221 @@ +//===--- 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 { +namespace { + +class FunctionASTVisitor : public RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + +public: + bool VisitVarDecl(VarDecl *VD) { + // Do not count function params. + // Do not count decomposition declarations (C++17's structured bindings). + if (StructNesting == 0 && + !(isa(VD) || isa(VD))) + ++Info.Variables; + return true; + } + bool VisitBindingDecl(BindingDecl *BD) { + // Do count each of the bindings (in the decomposition declaration). + if (StructNesting == 0) + ++Info.Variables; + return true; + } + + 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; + } + + bool TraverseLambdaExpr(LambdaExpr *Node) { + ++StructNesting; + Base::TraverseLambdaExpr(Node); + --StructNesting; + return true; + } + + bool TraverseCXXRecordDecl(CXXRecordDecl *Node) { + ++StructNesting; + Base::TraverseCXXRecordDecl(Node); + --StructNesting; + return true; + } + + bool TraverseStmtExpr(StmtExpr *SE) { + ++StructNesting; + Base::TraverseStmtExpr(SE); + --StructNesting; + return true; + } + + struct FunctionInfo { + unsigned Lines = 0; + unsigned Statements = 0; + unsigned Branches = 0; + unsigned NestingThreshold = 0; + unsigned Variables = 0; + std::vector NestingThresholders; + }; + FunctionInfo Info; + std::vector TrackedParent; + unsigned StructNesting = 0; + unsigned CurrentNestingLevel = 0; +}; + +} // namespace + +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)), + VariableThreshold(Options.get("VariableThreshold", -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); + Options.store(Opts, "VariableThreshold", VariableThreshold); +} + +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() || FI.Variables > VariableThreshold) { + 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; + } + + if (FI.Variables > VariableThreshold) { + diag(Func->getLocation(), "%0 variables (threshold %1)", + DiagnosticIDs::Note) + << FI.Variables << VariableThreshold; + } +} + +} // 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..7defccdb0 --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.h @@ -0,0 +1,59 @@ +//===--- 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). +/// * `VariableThreshold` - flag functions having a high number of variable +/// declarations. The default is `-1` (ignore the number of variables). +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; + const unsigned VariableThreshold; +}; + +} // 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..46919d3fc --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -0,0 +1,942 @@ +//===--- 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) \ + m(ObjcIvar) \ + +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_ObjcIvar]) + return SK_ObjcIvar; + + 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.isConstQualified()) { + if (NamingStyles[SK_ConstantMember]) + return SK_ConstantMember; + + if (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.isConstQualified()) { + if (NamingStyles[SK_ConstantParameter]) + return SK_ConstantParameter; + + if (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.isConstQualified()) { + if (Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant]) + return SK_ClassConstant; + + if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant]) + return SK_GlobalConstant; + + if (Decl->isStaticLocal() && NamingStyles[SK_StaticConstant]) + return SK_StaticConstant; + + if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant]) + return SK_LocalConstant; + + if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalConstant]) + return SK_LocalConstant; + + if (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) { + LLVM_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) { + LLVM_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/ImplicitBoolConversionCheck.cpp b/clang-tidy/readability/ImplicitBoolConversionCheck.cpp new file mode 100644 index 000000000..79022d425 --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolConversionCheck.cpp @@ -0,0 +1,388 @@ +//===--- ImplicitBoolConversionCheck.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 "ImplicitBoolConversionCheck.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 isCastAllowedInCondition(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 + +ImplicitBoolConversionCheck::ImplicitBoolConversionCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowIntegerConditions(Options.get("AllowIntegerConditions", false)), + AllowPointerConditions(Options.get("AllowPointerConditions", false)) {} + +void ImplicitBoolConversionCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions); + Options.store(Opts, "AllowPointerConditions", AllowPointerConditions); +} + +void ImplicitBoolConversionCheck::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 ImplicitBoolConversionCheck::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 ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast, + const Stmt *Parent, + ASTContext &Context) { + if (AllowPointerConditions && + (Cast->getCastKind() == CK_PointerToBoolean || + Cast->getCastKind() == CK_MemberPointerToBoolean) && + isCastAllowedInCondition(Cast, Context)) { + return; + } + + if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean && + isCastAllowedInCondition(Cast, Context)) { + return; + } + + auto Diag = diag(Cast->getLocStart(), "implicit conversion %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 ImplicitBoolConversionCheck::handleCastFromBool( + const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast, + ASTContext &Context) { + QualType DestType = + NextImplicitCast ? NextImplicitCast->getType() : Cast->getType(); + auto Diag = diag(Cast->getLocStart(), "implicit conversion 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/ImplicitBoolConversionCheck.h b/clang-tidy/readability/ImplicitBoolConversionCheck.h new file mode 100644 index 000000000..bb062e02e --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolConversionCheck.h @@ -0,0 +1,46 @@ +//===--- ImplicitBoolConversionCheck.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_CONVERSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CONVERSION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Checks for use of implicit bool conversions in expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-conversion.html +class ImplicitBoolConversionCheck : public ClangTidyCheck { +public: + ImplicitBoolConversionCheck(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); + + const bool AllowIntegerConditions; + const bool AllowPointerConditions; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CONVERSION_H diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp new file mode 100644 index 000000000..f1d0036c6 --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -0,0 +1,360 @@ +//===--- 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; +} + +bool nameMatch(StringRef L, StringRef R, bool Strict) { + if (Strict) + return L.empty() || R.empty() || L == R; + // We allow two names if one is a prefix/suffix of the other, ignoring case. + // Important special case: this is true if either parameter has no name! + return L.startswith_lower(R) || R.startswith_lower(L) || + L.endswith_lower(R) || R.endswith_lower(L); +} + +DifferingParamsContainer +findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OtherDeclaration, + const FunctionDecl *OriginalDeclaration, + bool Strict) { + 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 (!nameMatch(SourceParamName, OtherParamName, Strict)) { + 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 +findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration, + const FunctionDecl *ParameterSourceDeclaration, + SourceManager &SM, bool Strict) { + 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, Strict); + 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::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); + Options.store(Opts, "Strict", Strict); +} + +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 = + findInconsistentDeclarations(OriginalDeclaration, + ParameterSourceDeclaration, + *Result.SourceManager, Strict); + if (InconsistentDeclarations.empty()) { + // Avoid unnecessary further visits. + markRedeclarationsAsVisited(OriginalDeclaration); + return; + } + + SourceLocation StartLoc = OriginalDeclaration->getLocStart(); + if (StartLoc.isMacroID() && IgnoreMacros) { + 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..602856fa2 --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h @@ -0,0 +1,50 @@ +//===- 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), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", 1) != 0), + Strict(Options.getLocalOrGlobal("Strict", 0) != 0) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + 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; + const bool IgnoreMacros; + const bool Strict; +}; + +} // 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..409aba90e --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.cpp @@ -0,0 +1,196 @@ +//===--- 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; +} + +static std::string getNamespaceComment(const std::string &NameSpaceName, + bool InsertLineBreak) { + std::string Fix = "// namespace "; + Fix.append(NameSpaceName); + 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; + SourceLocation LBracketLocation = ND->getLocation(); + SourceLocation NestedNamespaceBegin = LBracketLocation; + + // Currently for nested namepsace (n1::n2::...) the AST matcher will match foo + // then bar instead of a single match. So if we got a nested namespace we have + // to skip the next ones. + for (const auto &EndOfNameLocation : Ends) { + if (Sources.isBeforeInTranslationUnit(NestedNamespaceBegin, + EndOfNameLocation)) + return; + } + + // Ignore macros + if (!ND->getLocation().isMacroID()) { + while (Lexer::getRawToken(LBracketLocation, Tok, Sources, getLangOpts()) || + !Tok.is(tok::l_brace)) { + LBracketLocation = LBracketLocation.getLocWithOffset(1); + } + } + + auto TextRange = + Lexer::getAsCharRange(SourceRange(NestedNamespaceBegin, LBracketLocation), + Sources, getLangOpts()); + StringRef NestedNamespaceName = + Lexer::getSourceText(TextRange, Sources, getLangOpts()).rtrim(); + bool IsNested = NestedNamespaceName.contains(':'); + + if (IsNested) + Ends.push_back(LBracketLocation); + else + NestedNamespaceName = ND->getName(); + + // 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] : ""; + + if (IsNested && NestedNamespaceName == NamespaceNameInComment) { + // C++17 nested namespace. + return; + } else if ((ND->isAnonymousNamespace() && + NamespaceNameInComment.empty()) || + (ND->getNameAsString() == NamespaceNameInComment && + Anonymous.empty())) { + // Check if the namespace in the comment is the same. + // 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 '" + NestedNamespaceName.str() + "'"); + + diag(AfterRBrace, Message) + << NamespaceName + << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OldCommentRange), + std::string(SpacesBeforeComments, ' ') + + (IsNested + ? getNamespaceComment(NestedNamespaceName, NeedLineBreak) + : 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..1b1a23154 --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.h @@ -0,0 +1,44 @@ +//===--- 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; + llvm::SmallVector Ends; +}; + +} // 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..bc6507c86 --- /dev/null +++ b/clang-tidy/readability/NonConstParameterCheck.cpp @@ -0,0 +1,223 @@ +//===--- 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; + + SmallVector Fixes; + auto *Function = + dyn_cast_or_null(Par->getParentFunctionOrMethod()); + if (!Function) + continue; + unsigned Index = Par->getFunctionScopeIndex(); + for (FunctionDecl *FnDecl : Function->redecls()) + Fixes.push_back(FixItHint::CreateInsertion( + FnDecl->getParamDecl(Index)->getLocStart(), "const ")); + + diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const") + << Par->getName() << Fixes; + } +} + +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..be1ae4388 --- /dev/null +++ b/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -0,0 +1,116 @@ +//===--- 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 "ImplicitBoolConversionCheck.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 "SimplifySubscriptExprCheck.h" +#include "StaticAccessedThroughInstanceCheck.h" +#include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "StringCompareCheck.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-conversion"); + 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-simplify-subscript-expr"); + CheckFactories.registerCheck( + "readability-static-accessed-through-instance"); + CheckFactories.registerCheck( + "readability-static-definition-in-anonymous-namespace"); + CheckFactories.registerCheck( + "readability-string-compare"); + 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..1df3f0555 --- /dev/null +++ b/clang-tidy/readability/RedundantDeclarationCheck.cpp @@ -0,0 +1,82 @@ +//===--- 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 { + +RedundantDeclarationCheck::RedundantDeclarationCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void RedundantDeclarationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + namedDecl(anyOf(varDecl(unless(isDefinition())), + functionDecl(unless(anyOf(isDefinition(), isDefaulted(), + hasParent(friendDecl())))))) + .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; + if (IgnoreMacros && + (D->getLocation().isMacroID() || Prev->getLocation().isMacroID())) + return; + // Don't complain when the previous declaration is a friend declaration. + for (const auto &Parent : Result.Context->getParents(*Prev)) + if (Parent.get()) + 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..9be79b848 --- /dev/null +++ b/clang-tidy/readability/RedundantDeclarationCheck.h @@ -0,0 +1,37 @@ +//===--- 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); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool IgnoreMacros; +}; + +} // 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..8409f9f40 --- /dev/null +++ b/clang-tidy/readability/RedundantMemberInitCheck.cpp @@ -0,0 +1,69 @@ +//===--- 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()))), + unless(forField(hasParent(recordDecl(isUnion()))))) + .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->getAnyMember() + << 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..1abd70bd0 --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp @@ -0,0 +1,147 @@ +//===--- 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); + + // Catch '!ptr.get()' + const auto CallToGetAsBool = ignoringParenImpCasts(callToGet(recordDecl( + QuacksLikeASmartptr, has(cxxConversionDecl(returns(booleanType())))))); + Finder->addMatcher( + unaryOperator(hasOperatorName("!"), hasUnaryOperand(CallToGetAsBool)), + Callback); + + // Catch 'if(ptr.get())' + Finder->addMatcher(ifStmt(hasCondition(CallToGetAsBool)), Callback); + + // Catch 'ptr.get() ? X : Y' + Finder->addMatcher(conditionalOperator(hasCondition(CallToGetAsBool)), + 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); + + // 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..21f4a8a5d --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.cpp @@ -0,0 +1,199 @@ +//===- 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 = type(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(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..46ce2a47b --- /dev/null +++ b/clang-tidy/readability/RedundantStringInitCheck.cpp @@ -0,0 +1,71 @@ +//===- 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(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(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..ba8e5b42c --- /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 && Literal->getLocStart().isMacroID()) ? 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(); + if (const auto *EC = dyn_cast(E)) + E = EC->getSubExpr(); + + 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/SimplifySubscriptExprCheck.cpp b/clang-tidy/readability/SimplifySubscriptExprCheck.cpp new file mode 100644 index 000000000..28cc576dd --- /dev/null +++ b/clang-tidy/readability/SimplifySubscriptExprCheck.cpp @@ -0,0 +1,76 @@ +//===--- SimplifySubscriptExprCheck.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 "SimplifySubscriptExprCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +static const char kDefaultTypes[] = + "::std::basic_string;::std::basic_string_view;::std::vector;::std::array"; + +SimplifySubscriptExprCheck::SimplifySubscriptExprCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Types(utils::options::parseStringList( + Options.get("Types", kDefaultTypes))) { +} + +void SimplifySubscriptExprCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto TypesMatcher = hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl(hasAnyName( + llvm::SmallVector(Types.begin(), Types.end())))))); + + Finder->addMatcher( + arraySubscriptExpr(hasBase(ignoringParenImpCasts( + cxxMemberCallExpr( + has(memberExpr().bind("member")), + on(hasType(qualType( + unless(anyOf(substTemplateTypeParmType(), + hasDescendant(substTemplateTypeParmType()))), + anyOf(TypesMatcher, pointerType(pointee(TypesMatcher)))))), + callee(namedDecl(hasName("data")))) + .bind("call")))), + this); +} + +void SimplifySubscriptExprCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + if (Result.Context->getSourceManager().isMacroBodyExpansion( + Call->getExprLoc())) + return; + + const auto *Member = Result.Nodes.getNodeAs("member"); + auto DiagBuilder = + diag(Member->getMemberLoc(), + "accessing an element of the container does not require a call to " + "'data()'; did you mean to use 'operator[]'?"); + if (Member->isArrow()) + DiagBuilder << FixItHint::CreateInsertion(Member->getLocStart(), "(*") + << FixItHint::CreateInsertion(Member->getOperatorLoc(), ")"); + DiagBuilder << FixItHint::CreateRemoval( + {Member->getOperatorLoc(), Call->getLocEnd()}); +} + +void SimplifySubscriptExprCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Types", utils::options::serializeStringList(Types)); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/SimplifySubscriptExprCheck.h b/clang-tidy/readability/SimplifySubscriptExprCheck.h new file mode 100644 index 000000000..86a21b914 --- /dev/null +++ b/clang-tidy/readability/SimplifySubscriptExprCheck.h @@ -0,0 +1,38 @@ +//===--- SimplifySubscriptExprCheck.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_SIMPLIFYSUBSCRIPTEXPRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFYSUBSCRIPTEXPRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Simplifies subscript expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-simplify-subscript-expr.html +class SimplifySubscriptExprCheck : public ClangTidyCheck { +public: + SimplifySubscriptExprCheck(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 Types; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFYSUBSCRIPTEXPRCHECK_H diff --git a/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp b/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp new file mode 100644 index 000000000..b1365772b --- /dev/null +++ b/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp @@ -0,0 +1,90 @@ +//===--- StaticAccessedThroughInstanceCheck.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 "StaticAccessedThroughInstanceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +static unsigned getNameSpecifierNestingLevel(const QualType &QType) { + if (const ElaboratedType *ElType = QType->getAs()) { + const NestedNameSpecifier *NestedSpecifiers = ElType->getQualifier(); + unsigned NameSpecifierNestingLevel = 1; + do { + NameSpecifierNestingLevel++; + NestedSpecifiers = NestedSpecifiers->getPrefix(); + } while (NestedSpecifiers); + + return NameSpecifierNestingLevel; + } + return 0; +} + +void StaticAccessedThroughInstanceCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "NameSpecifierNestingThreshold", + NameSpecifierNestingThreshold); +} + +void StaticAccessedThroughInstanceCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + memberExpr(hasDeclaration(anyOf(cxxMethodDecl(isStaticStorageClass()), + varDecl(hasStaticStorageDuration()))), + unless(isInTemplateInstantiation())) + .bind("memberExpression"), + this); +} + +void StaticAccessedThroughInstanceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MemberExpression = + Result.Nodes.getNodeAs("memberExpression"); + + if (MemberExpression->getLocStart().isMacroID()) + return; + + const Expr *BaseExpr = MemberExpression->getBase(); + + // Do not warn for overlaoded -> operators. + if (isa(BaseExpr)) + return; + + QualType BaseType = + BaseExpr->getType()->isPointerType() + ? BaseExpr->getType()->getPointeeType().getUnqualifiedType() + : BaseExpr->getType().getUnqualifiedType(); + + const ASTContext *AstContext = Result.Context; + PrintingPolicy PrintingPolicyWithSupressedTag(AstContext->getLangOpts()); + PrintingPolicyWithSupressedTag.SuppressTagKeyword = true; + std::string BaseTypeName = + BaseType.getAsString(PrintingPolicyWithSupressedTag); + + SourceLocation MemberExprStartLoc = MemberExpression->getLocStart(); + auto Diag = + diag(MemberExprStartLoc, "static member accessed through instance"); + + if (BaseExpr->HasSideEffects(*AstContext) || + getNameSpecifierNestingLevel(BaseType) > NameSpecifierNestingThreshold) + return; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(MemberExprStartLoc, + MemberExpression->getMemberLoc()), + BaseTypeName + "::"); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h b/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h new file mode 100644 index 000000000..c2eebabe8 --- /dev/null +++ b/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h @@ -0,0 +1,43 @@ +//===--- StaticAccessedThroughInstanceCheck.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_ACCESSED_THROUGH_INSTANCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_ACCESSED_THROUGH_INSTANCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \@brief Checks for member expressions that access static members through +/// instances and replaces them with uses of the appropriate qualified-id. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-static-accessed-through-instance.html +class StaticAccessedThroughInstanceCheck : public ClangTidyCheck { +public: + StaticAccessedThroughInstanceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NameSpecifierNestingThreshold( + Options.get("NameSpecifierNestingThreshold", 3)) {} + + 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 NameSpecifierNestingThreshold; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_ACCESSED_THROUGH_INSTANCE_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/StringCompareCheck.cpp b/clang-tidy/readability/StringCompareCheck.cpp new file mode 100644 index 000000000..e75e80bb2 --- /dev/null +++ b/clang-tidy/readability/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 readability { + +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 readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/StringCompareCheck.h b/clang-tidy/readability/StringCompareCheck.h new file mode 100644 index 000000000..248d2ee8e --- /dev/null +++ b/clang-tidy/readability/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_READABILITY_STRINGCOMPARECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// 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/readability-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 readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_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..53a5ff921 --- /dev/null +++ b/clang-tidy/rename_check.py @@ -0,0 +1,272 @@ +#!/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 argparse +import glob +import os +import re + + +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("Replacing '%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 or sFrom == sTo: + return fileName + newFileName = fileName.replace(sFrom, sTo) + print("Renaming '%s' -> '%s'..." % (fileName, newFileName)) + os.rename(fileName, newFileName) + return newFileName + +def deleteMatchingLines(fileName, pattern): + lines = None + with open(fileName, "r") as f: + lines = f.readlines() + + not_matching_lines = [l for l in lines if not re.search(pattern, l)] + if len(not_matching_lines) == len(lines): + return False + + print("Removing lines matching '%s' in '%s'..." % (pattern, fileName)) + print(' ' + ' '.join([l for l in lines if re.search(pattern, l)])) + with open(fileName, "w") as f: + f.writelines(not_matching_lines) + + return True + +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)] + +# 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 + +# 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 "' + 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(clang_tidy_path, old_check_name, new_check_name): + filename = os.path.normpath(os.path.join(clang_tidy_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(""" +- The '%s' check was renamed to :doc:`%s + ` +""" % (old_check_name, new_check_name, new_check_name)) + note_added = True + + f.write(line) + +def main(): + parser = argparse.ArgumentParser(description='Rename clang-tidy check.') + parser.add_argument('old_check_name', type=str, + help='Old check name.') + parser.add_argument('new_check_name', type=str, + help='New check name.') + parser.add_argument('--check_class_name', type=str, + help='Old name of the class implementing the check.') + args = parser.parse_args() + + old_module = args.old_check_name.split('-')[0] + new_module = args.new_check_name.split('-')[0] + if args.check_class_name: + check_name_camel = args.check_class_name + else: + check_name_camel = (''.join(map(lambda elem: elem.capitalize(), + args.old_check_name.split('-')[1:])) + + 'Check') + + new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(), + args.new_check_name.split('-')[1:])) + + 'Check') + + clang_tidy_path = os.path.dirname(__file__) + + header_guard_variants = [ + (old_module + '_' + new_check_name_camel).upper(), + args.old_check_name.replace('-', '_').upper()] + header_guard_new = (new_module + '_' + new_check_name_camel).upper() + + old_module_path = os.path.join(clang_tidy_path, old_module) + new_module_path = os.path.join(clang_tidy_path, new_module) + + # Remove the check from the old module. + cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt') + check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel) + if not check_found: + print("Check name '%s' not found in %s. Exiting." % + (check_name_camel, cmake_lists)) + return 1 + + modulecpp = filter( + lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp', + os.listdir(old_module_path))[0] + deleteMatchingLines(os.path.join(old_module_path, modulecpp), + '\\b' + check_name_camel + '|\\b' + args.old_check_name) + + 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, new_check_name_camel) + replaceInFile(filename, generateCommentLineHeader(originalName), + generateCommentLineHeader(filename)) + replaceInFile(filename, generateCommentLineSource(originalName), + generateCommentLineSource(filename)) + for header_guard in header_guard_variants: + replaceInFile(filename, header_guard, header_guard_new) + + if args.new_check_name + '.rst' in filename: + replaceInFile( + filename, + args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n', + args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n') + + replaceInFile(filename, args.old_check_name, args.new_check_name) + replaceInFile(filename, old_module + '::' + check_name_camel, + new_module + '::' + new_check_name_camel) + replaceInFile(filename, old_module + '/' + check_name_camel, + new_module + '/' + new_check_name_camel) + replaceInFile(filename, check_name_camel, new_check_name_camel) + + if old_module != new_module: + check_implementation_files = glob.glob( + os.path.join(old_module_path, new_check_name_camel + '*')) + for filename in check_implementation_files: + # Move check implementation to the directory of the new module. + filename = fileRename(filename, old_module_path, new_module_path) + replaceInFile(filename, 'namespace ' + old_module, + 'namespace ' + new_module) + + # Add check to the new module. + adapt_cmake(new_module_path, new_check_name_camel) + adapt_module(new_module_path, new_module, args.new_check_name, + new_check_name_camel) + + os.system(os.path.join(clang_tidy_path, 'add_new_check.py') + + ' --update-docs') + add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/tool/CMakeLists.txt b/clang-tidy/tool/CMakeLists.txt new file mode 100644 index 000000000..a3ec4ae75 --- /dev/null +++ b/clang-tidy/tool/CMakeLists.txt @@ -0,0 +1,47 @@ +set(LLVM_LINK_COMPONENTS + AllTargetsAsmParsers + AllTargetsDescs + AllTargetsInfos + support + ) + +add_clang_tool(clang-tidy + ClangTidyMain.cpp + ) +add_dependencies(clang-tidy + clang-headers + ) +target_link_libraries(clang-tidy + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangTidy + clangTidyAndroidModule + clangTidyAbseilModule + clangTidyBoostModule + clangTidyBugproneModule + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyFuchsiaModule + clangTidyGoogleModule + clangTidyHICPPModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyMPIModule + clangTidyObjCModule + clangTidyPerformanceModule + clangTidyPortabilityModule + clangTidyReadabilityModule + clangTidyZirconModule + clangTooling + clangToolingCore + ) + +install(PROGRAMS clang-tidy-diff.py + DESTINATION share/clang + COMPONENT clang-tidy) +install(PROGRAMS run-clang-tidy.py + DESTINATION share/clang + COMPONENT clang-tidy) diff --git a/clang-tidy/tool/ClangTidyMain.cpp b/clang-tidy/tool/ClangTidyMain.cpp new file mode 100644 index 000000000..b458b2931 --- /dev/null +++ b/clang-tidy/tool/ClangTidyMain.cpp @@ -0,0 +1,577 @@ +//===--- 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" +#include "llvm/Support/TargetSelect.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: '' + 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 StoreCheckProfile("store-check-profile", + cl::desc(R"( +By default reports are printed in tabulated +format to stderr. When this option is passed, +these per-TU profiles are instead stored as JSON. +)"), + cl::value_desc("prefix"), + cl::cat(ClangTidyCategory)); + +/// This option allows enabling the experimental alpha checkers from the static +/// analyzer. This option is set to false and not visible in help, because it is +/// highly not recommended for users. +static cl::opt + AllowEnablingAnalyzerAlphaCheckers("allow-enabling-analyzer-alpha-checkers", + cl::init(false), cl::Hidden, + 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)); + +static cl::opt VfsOverlay("vfsoverlay", cl::desc(R"( +Overlay the virtual filesystem described by file +over the real file system. +)"), + cl::value_desc("filename"), + 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 std::unique_ptr createOptionsProvider( + llvm::IntrusiveRefCntPtr FS) { + 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.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 (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, std::move(FS)); +} + +llvm::IntrusiveRefCntPtr +getVfsOverlayFromFile(const std::string &OverlayFile) { + llvm::IntrusiveRefCntPtr OverlayFS( + new vfs::OverlayFileSystem(vfs::getRealFileSystem())); + llvm::ErrorOr> Buffer = + OverlayFS->getBufferForFile(OverlayFile); + if (!Buffer) { + llvm::errs() << "Can't load virtual filesystem overlay file '" + << OverlayFile << "': " << Buffer.getError().message() + << ".\n"; + return nullptr; + } + + IntrusiveRefCntPtr FS = vfs::getVFSFromYAML( + std::move(Buffer.get()), /*DiagHandler*/ nullptr, OverlayFile); + if (!FS) { + llvm::errs() << "Error: invalid virtual filesystem overlay file '" + << OverlayFile << "'.\n"; + return nullptr; + } + OverlayFS->pushOverlay(FS); + return OverlayFS; +} + +static int clangTidyMain(int argc, const char **argv) { + CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory, + cl::ZeroOrMore); + llvm::IntrusiveRefCntPtr BaseFS( + VfsOverlay.empty() ? vfs::getRealFileSystem() + : getVfsOverlayFromFile(VfsOverlay)); + if (!BaseFS) + return 1; + + auto OwningOptionsProvider = createOptionsProvider(BaseFS); + auto *OptionsProvider = OwningOptionsProvider.get(); + if (!OptionsProvider) + return 1; + + auto MakeAbsolute = [](const std::string &Input) -> SmallString<256> { + if (Input.empty()) + return {}; + SmallString<256> AbsolutePath(Input); + if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) { + llvm::errs() << "Can't make absolute path from " << Input << ": " + << EC.message() << "\n"; + } + return AbsolutePath; + }; + + SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile); + + StringRef FileName("dummy"); + auto PathList = OptionsParser.getSourcePathList(); + if (!PathList.empty()) { + FileName = PathList.front(); + } + + SmallString<256> FilePath = MakeAbsolute(FileName); + + ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath); + std::vector EnabledChecks = + getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers); + + 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, AllowEnablingAnalyzerAlphaCheckers); + 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; + } + + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + + ClangTidyContext Context(std::move(OwningOptionsProvider), + AllowEnablingAnalyzerAlphaCheckers); + runClangTidy(Context, OptionsParser.getCompilations(), PathList, BaseFS, + EnableCheckProfile, ProfilePrefix); + ArrayRef Errors = Context.getErrors(); + bool FoundErrors = llvm::find_if(Errors, [](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, + BaseFS); + + 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 (WErrorCount) { + if (!Quiet) { + StringRef Plural = WErrorCount == 1 ? "" : "s"; + llvm::errs() << WErrorCount << " warning" << Plural << " treated as error" + << Plural << "\n"; + } + return WErrorCount; + } + + if (FoundErrors) { + // TODO: Figure out when zero exit code should be used with -fix-errors: + // a. when a fix has been applied for an error + // b. when a fix has been applied for all errors + // c. some other condition. + // For now always returning zero when -fix-errors is used. + if (FixErrors) + return 0; + if (!Quiet) + llvm::errs() << "Found compiler error(s).\n"; + return 1; + } + + 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 AbseilModule. +extern volatile int AbseilModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED AbseilModuleAnchorDestination = + AbseilModuleAnchorSource; + +// 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 FuchsiaModule. +extern volatile int FuchsiaModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED FuchsiaModuleAnchorDestination = + FuchsiaModuleAnchorSource; + +// This anchor is used to force the linker to link the GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// This anchor is used to force the linker to link the 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 PortabilityModule. +extern volatile int PortabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED PortabilityModuleAnchorDestination = + PortabilityModuleAnchorSource; + +// This anchor is used to force the linker to link the ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +// This anchor is used to force the linker to link the ObjCModule. +extern volatile int ObjCModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ObjCModuleAnchorDestination = + ObjCModuleAnchorSource; + +// This anchor is used to force the linker to link the HICPPModule. +extern volatile int HICPPModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED HICPPModuleAnchorDestination = + HICPPModuleAnchorSource; + +// This anchor is used to force the linker to link the ZirconModule. +extern volatile int ZirconModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ZirconModuleAnchorDestination = + ZirconModuleAnchorSource; + +} // 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..ce46c0ed3 --- /dev/null +++ b/clang-tidy/tool/run-clang-tidy.py @@ -0,0 +1,313 @@ +#!/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 glob +import json +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback +import yaml + +is_py2 = sys.version[0] == '2' + +if is_py2: + import Queue as queue +else: + import queue as queue + +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 make_absolute(f, directory): + if os.path.isabs(f): + return f + return os.path.normpath(os.path.join(directory, f)) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter, extra_arg, extra_arg_before, quiet, + config): + """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') + if config: + start.append('-config=' + config) + start.append(f) + return start + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey="Diagnostics" + merged=[] + for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): + content = yaml.safe_load(open(replacefile, 'r')) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = { 'MainSourceFile': '', mergekey: merged } + with open(mergefile, 'w') as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, 'w').close() + + +def 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.""" + 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, failed_files): + """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, args.config) + sys.stdout.write(' '.join(invocation) + '\n') + return_code = subprocess.call(invocation) + if return_code != 0: + failed_files.append(name) + 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('-config', default=None, + help='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.') + 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('-export-fixes', metavar='filename', dest='export_fixes', + help='Create a yaml file to store suggested fixes in, ' + 'which can be applied with clang-apply-replacements.') + 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('-') + subprocess.check_call(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 = [make_absolute(entry['file'], entry['directory']) + for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + tmpdir = None + if args.fix or args.export_fixes: + 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)) + + return_code = 0 + try: + # Spin up a bunch of tidy-launching threads. + task_queue = queue.Queue(max_task) + # List of files with a non-zero return code. + failed_files = [] + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, build_path, task_queue, failed_files)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + task_queue.put(name) + + # Wait for all threads to be done. + task_queue.join() + if len(failed_files): + return_code = 1 + + 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 tmpdir: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if args.export_fixes: + print('Writing fixes to ' + args.export_fixes + ' ...') + try: + merge_replacement_files(tmpdir, args.export_fixes) + except: + print('Error exporting fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code=1 + + if args.fix: + print('Applying fixes ...') + try: + apply_fixes(args, tmpdir) + except: + print('Error applying fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code=1 + + if tmpdir: + shutil.rmtree(tmpdir) + sys.exit(return_code) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/utils/ASTUtils.cpp b/clang-tidy/utils/ASTUtils.cpp new file mode 100644 index 000000000..ab6077a74 --- /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 macro 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..487b09ac8 --- /dev/null +++ b/clang-tidy/utils/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyUtils + ASTUtils.cpp + DeclRefExprUtils.cpp + ExprMutationAnalyzer.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/ExprMutationAnalyzer.cpp b/clang-tidy/utils/ExprMutationAnalyzer.cpp new file mode 100644 index 000000000..424fa8f67 --- /dev/null +++ b/clang-tidy/utils/ExprMutationAnalyzer.cpp @@ -0,0 +1,260 @@ +//===---------- ExprMutationAnalyzer.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 "ExprMutationAnalyzer.h" + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/STLExtras.h" + +namespace clang { +namespace tidy { +namespace utils { +using namespace ast_matchers; + +namespace { + +AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) { + return llvm::is_contained(Node.capture_inits(), E); +} + +AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt, + ast_matchers::internal::Matcher, InnerMatcher) { + const DeclStmt *const Range = Node.getRangeStmt(); + return InnerMatcher.matches(*Range, Finder, Builder); +} + +const ast_matchers::internal::VariadicDynCastAllOfMatcher + cxxTypeidExpr; + +AST_MATCHER(CXXTypeidExpr, isPotentiallyEvaluated) { + return Node.isPotentiallyEvaluated(); +} + +const ast_matchers::internal::VariadicDynCastAllOfMatcher + cxxNoexceptExpr; + +const ast_matchers::internal::VariadicDynCastAllOfMatcher + genericSelectionExpr; + +AST_MATCHER_P(GenericSelectionExpr, hasControllingExpr, + ast_matchers::internal::Matcher, InnerMatcher) { + return InnerMatcher.matches(*Node.getControllingExpr(), Finder, Builder); +} + +const auto nonConstReferenceType = [] { + return referenceType(pointee(unless(isConstQualified()))); +}; + +} // namespace + +const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) { + const auto Memoized = Results.find(Exp); + if (Memoized != Results.end()) + return Memoized->second; + + if (isUnevaluated(Exp)) + return Results[Exp] = nullptr; + + for (const auto &Finder : {&ExprMutationAnalyzer::findDirectMutation, + &ExprMutationAnalyzer::findMemberMutation, + &ExprMutationAnalyzer::findArrayElementMutation, + &ExprMutationAnalyzer::findCastMutation, + &ExprMutationAnalyzer::findRangeLoopMutation, + &ExprMutationAnalyzer::findReferenceMutation}) { + if (const Stmt *S = (this->*Finder)(Exp)) + return Results[Exp] = S; + } + + return Results[Exp] = nullptr; +} + +bool ExprMutationAnalyzer::isUnevaluated(const Expr *Exp) { + return selectFirst( + "expr", + match( + findAll( + expr(equalsNode(Exp), + anyOf( + // `Exp` is part of the underlying expression of + // decltype/typeof if it has an ancestor of + // typeLoc. + hasAncestor(typeLoc(unless( + hasAncestor(unaryExprOrTypeTraitExpr())))), + hasAncestor(expr(anyOf( + // `UnaryExprOrTypeTraitExpr` is unevaluated + // unless it's sizeof on VLA. + unaryExprOrTypeTraitExpr(unless(sizeOfExpr( + hasArgumentOfType(variableArrayType())))), + // `CXXTypeidExpr` is unevaluated unless it's + // applied to an expression of glvalue of + // polymorphic class type. + cxxTypeidExpr( + unless(isPotentiallyEvaluated())), + // The controlling expression of + // `GenericSelectionExpr` is unevaluated. + genericSelectionExpr(hasControllingExpr( + hasDescendant(equalsNode(Exp)))), + cxxNoexceptExpr()))))) + .bind("expr")), + *Stm, *Context)) != nullptr; +} + +const Stmt * +ExprMutationAnalyzer::findExprMutation(ArrayRef Matches) { + for (const auto &Nodes : Matches) { + if (const Stmt *S = findMutation(Nodes.getNodeAs("expr"))) + return S; + } + return nullptr; +} + +const Stmt * +ExprMutationAnalyzer::findDeclMutation(ArrayRef Matches) { + for (const auto &DeclNodes : Matches) { + if (const Stmt *S = findDeclMutation(DeclNodes.getNodeAs("decl"))) + return S; + } + return nullptr; +} + +const Stmt *ExprMutationAnalyzer::findDeclMutation(const Decl *Dec) { + const auto Refs = match( + findAll(declRefExpr(to(equalsNode(Dec))).bind("expr")), *Stm, *Context); + for (const auto &RefNodes : Refs) { + const auto *E = RefNodes.getNodeAs("expr"); + if (findMutation(E)) + return E; + } + return nullptr; +} + +const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) { + // LHS of any assignment operators. + const auto AsAssignmentLhs = + binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp))); + + // Operand of increment/decrement operators. + const auto AsIncDecOperand = + unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), + hasUnaryOperand(equalsNode(Exp))); + + // Invoking non-const member function. + const auto NonConstMethod = cxxMethodDecl(unless(isConst())); + const auto AsNonConstThis = + expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))), + cxxOperatorCallExpr(callee(NonConstMethod), + hasArgument(0, equalsNode(Exp))))); + + // Taking address of 'Exp'. + // We're assuming 'Exp' is mutated as soon as its address is taken, though in + // theory we can follow the pointer and see whether it escaped `Stm` or is + // dereferenced and then mutated. This is left for future improvements. + const auto AsAmpersandOperand = + unaryOperator(hasOperatorName("&"), + // A NoOp implicit cast is adding const. + unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), + hasUnaryOperand(equalsNode(Exp))); + const auto AsPointerFromArrayDecay = + castExpr(hasCastKind(CK_ArrayToPointerDecay), + unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp))); + + // Used as non-const-ref argument when calling a function. + const auto NonConstRefParam = forEachArgumentWithParam( + equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType()))); + const auto AsNonConstRefArg = + anyOf(callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam)); + + // Captured by a lambda by reference. + // If we're initializing a capture with 'Exp' directly then we're initializing + // a reference capture. + // For value captures there will be an ImplicitCastExpr . + const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp)); + + // Returned as non-const-ref. + // If we're returning 'Exp' directly then it's returned as non-const-ref. + // For returning by value there will be an ImplicitCastExpr . + // For returning by const-ref there will be an ImplicitCastExpr (for + // adding const.) + const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp))); + + const auto Matches = + match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis, + AsAmpersandOperand, AsPointerFromArrayDecay, + AsNonConstRefArg, AsLambdaRefCaptureInit, + AsNonConstRefReturn)) + .bind("stmt")), + *Stm, *Context); + return selectFirst("stmt", Matches); +} + +const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) { + // Check whether any member of 'Exp' is mutated. + const auto MemberExprs = match( + findAll(memberExpr(hasObjectExpression(equalsNode(Exp))).bind("expr")), + *Stm, *Context); + return findExprMutation(MemberExprs); +} + +const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) { + // Check whether any element of an array is mutated. + const auto SubscriptExprs = match( + findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp)))) + .bind("expr")), + *Stm, *Context); + return findExprMutation(SubscriptExprs); +} + +const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) { + // If 'Exp' is casted to any non-const reference type, check the castExpr. + const auto Casts = + match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)), + anyOf(explicitCastExpr(hasDestinationType( + nonConstReferenceType())), + implicitCastExpr(hasImplicitDestinationType( + nonConstReferenceType())))) + .bind("expr")), + *Stm, *Context); + return findExprMutation(Casts); +} + +const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) { + // If range for looping over 'Exp' with a non-const reference loop variable, + // check all declRefExpr of the loop variable. + const auto LoopVars = + match(findAll(cxxForRangeStmt( + hasLoopVariable( + varDecl(hasType(nonConstReferenceType())).bind("decl")), + hasRangeInit(equalsNode(Exp)))), + *Stm, *Context); + return findDeclMutation(LoopVars); +} + +const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) { + // If 'Exp' is bound to a non-const reference, check all declRefExpr to that. + const auto Refs = match( + stmt(forEachDescendant( + varDecl( + hasType(nonConstReferenceType()), + hasInitializer(anyOf(equalsNode(Exp), + conditionalOperator(anyOf( + hasTrueExpression(equalsNode(Exp)), + hasFalseExpression(equalsNode(Exp)))))), + hasParent(declStmt().bind("stmt")), + // Don't follow the reference in range statement, we've handled + // that separately. + unless(hasParent(declStmt(hasParent( + cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt")))))))) + .bind("decl"))), + *Stm, *Context); + return findDeclMutation(Refs); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/ExprMutationAnalyzer.h b/clang-tidy/utils/ExprMutationAnalyzer.h new file mode 100644 index 000000000..256bb7128 --- /dev/null +++ b/clang-tidy/utils/ExprMutationAnalyzer.h @@ -0,0 +1,56 @@ +//===---------- ExprMutationAnalyzer.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_EXPRMUTATIONANALYZER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H + +#include + +#include "clang/AST/AST.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// Analyzes whether any mutative operations are applied to an expression within +/// a given statement. +class ExprMutationAnalyzer { +public: + ExprMutationAnalyzer(const Stmt *Stm, ASTContext *Context) + : Stm(Stm), Context(Context) {} + + bool isMutated(const Decl *Dec) { return findDeclMutation(Dec) != nullptr; } + bool isMutated(const Expr *Exp) { return findMutation(Exp) != nullptr; } + const Stmt *findMutation(const Expr *Exp); + +private: + bool isUnevaluated(const Expr *Exp); + + const Stmt *findExprMutation(ArrayRef Matches); + const Stmt *findDeclMutation(ArrayRef Matches); + const Stmt *findDeclMutation(const Decl *Dec); + + const Stmt *findDirectMutation(const Expr *Exp); + const Stmt *findMemberMutation(const Expr *Exp); + const Stmt *findArrayElementMutation(const Expr *Exp); + const Stmt *findCastMutation(const Expr *Exp); + const Stmt *findRangeLoopMutation(const Expr *Exp); + const Stmt *findReferenceMutation(const Expr *Exp); + + const Stmt *const Stm; + ASTContext *const Context; + llvm::DenseMap Results; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_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..201201718 --- /dev/null +++ b/clang-tidy/utils/HeaderFileExtensionsUtils.h @@ -0,0 +1,56 @@ +//===--- 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 Returns recommended default value for the list of header file +/// extensions. +inline StringRef defaultHeaderFileExtensions() { return ",h,hh,hpp,hxx"; } + +/// \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..a2d8288e8 --- /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", utils::defaultHeaderFileExtensions())) { + 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..64e4213c5 --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.cpp @@ -0,0 +1,86 @@ +//===-------- 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*/, + SrcMgr::CharacteristicKind /*FileType*/) 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..aeb639faf --- /dev/null +++ b/clang-tidy/utils/Matchers.h @@ -0,0 +1,55 @@ +//===--- 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, isAssignmentOperator) { + return Node.isAssignmentOp(); +} + +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..2cdc50647 --- /dev/null +++ b/clang-tidy/utils/TypeTraits.cpp @@ -0,0 +1,150 @@ +//===--- 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) && + !Type->isObjCLifetimeType(); +} + +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/clang-tidy/zircon/CMakeLists.txt b/clang-tidy/zircon/CMakeLists.txt new file mode 100644 index 000000000..7aa7cd3f6 --- /dev/null +++ b/clang-tidy/zircon/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyZirconModule + TemporaryObjectsCheck.cpp + ZirconTidyModule.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/zircon/TemporaryObjectsCheck.cpp b/clang-tidy/zircon/TemporaryObjectsCheck.cpp new file mode 100644 index 000000000..5fff67bb3 --- /dev/null +++ b/clang-tidy/zircon/TemporaryObjectsCheck.cpp @@ -0,0 +1,60 @@ +//===--- TemporaryObjectsCheck.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 "TemporaryObjectsCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace zircon { + +AST_MATCHER_P(CXXRecordDecl, matchesAnyName, ArrayRef, Names) { + std::string QualifiedName = Node.getQualifiedNameAsString(); + return llvm::any_of(Names, + [&](StringRef Name) { return QualifiedName == Name; }); +} + +void TemporaryObjectsCheck::registerMatchers(MatchFinder *Finder) { + // Matcher for default constructors. + Finder->addMatcher( + cxxTemporaryObjectExpr(hasDeclaration(cxxConstructorDecl(hasParent( + cxxRecordDecl(matchesAnyName(Names)))))) + .bind("temps"), + this); + + // Matcher for user-defined constructors. + Finder->addMatcher( + cxxConstructExpr(allOf(hasParent(cxxFunctionalCastExpr()), + hasDeclaration(cxxConstructorDecl(hasParent( + cxxRecordDecl(matchesAnyName(Names))))))) + .bind("temps"), + this); +} + +void TemporaryObjectsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *D = Result.Nodes.getNodeAs("temps")) + diag(D->getLocation(), + "creating a temporary object of type %q0 is prohibited") + << D->getConstructor()->getParent(); +} + +void TemporaryObjectsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Names", utils::options::serializeStringList(Names)); +} + +} // namespace zircon +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/zircon/TemporaryObjectsCheck.h b/clang-tidy/zircon/TemporaryObjectsCheck.h new file mode 100644 index 000000000..302ef72a6 --- /dev/null +++ b/clang-tidy/zircon/TemporaryObjectsCheck.h @@ -0,0 +1,42 @@ +//===--- TemporaryObjectsCheck.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_ZIRCON_TEMPORARYOBJECTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ZIRCON_TEMPORARYOBJECTSCHECK_H + +#include "../ClangTidy.h" +#include "../utils/OptionsUtils.h" + +namespace clang { +namespace tidy { +namespace zircon { + +/// Construction of specific temporary objects in the Zircon kernel is +/// discouraged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/zircon-temporary-objects.html +class TemporaryObjectsCheck : public ClangTidyCheck { +public: + TemporaryObjectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Names(utils::options::parseStringList(Options.get("Names", ""))) {} + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::vector Names; +}; + +} // namespace zircon +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ZIRCON_TEMPORARYOBJECTSCHECK_H diff --git a/clang-tidy/zircon/ZirconTidyModule.cpp b/clang-tidy/zircon/ZirconTidyModule.cpp new file mode 100644 index 000000000..3e53c23ac --- /dev/null +++ b/clang-tidy/zircon/ZirconTidyModule.cpp @@ -0,0 +1,40 @@ +//===--- ZirconTidyModule.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 "TemporaryObjectsCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace zircon { + +/// This module is for Zircon-specific checks. +class ZirconModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "zircon-temporary-objects"); + } +}; + +// Register the ZirconTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("zircon-module", "Adds Zircon kernel checks."); +} // namespace zircon + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ZirconModule. +volatile int ZirconModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clangd/AST.cpp b/clangd/AST.cpp new file mode 100644 index 000000000..ec1862cd7 --- /dev/null +++ b/clangd/AST.cpp @@ -0,0 +1,57 @@ +//===--- AST.cpp - Utility AST functions -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AST.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/Basic/SourceManager.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +SourceLocation findNameLoc(const clang::Decl* D) { + const auto& SM = D->getASTContext().getSourceManager(); + // FIXME: Revisit the strategy, the heuristic is limitted when handling + // macros, we should use the location where the whole definition occurs. + SourceLocation SpellingLoc = SM.getSpellingLoc(D->getLocation()); + if (D->getLocation().isMacroID()) { + std::string PrintLoc = SpellingLoc.printToString(SM); + if (llvm::StringRef(PrintLoc).startswith("")) { + // We use the expansion location for the following symbols, as spelling + // locations of these symbols are not interesting to us: + // * symbols formed via macro concatenation, the spelling location will + // be "" + // * symbols controlled and defined by a compile command-line option + // `-DName=foo`, the spelling location will be "". + SpellingLoc = SM.getExpansionRange(D->getLocation()).getBegin(); + } + } + return SpellingLoc; +} + +std::string printQualifiedName(const NamedDecl &ND) { + std::string QName; + llvm::raw_string_ostream OS(QName); + PrintingPolicy Policy(ND.getASTContext().getLangOpts()); + // Note that inline namespaces are treated as transparent scopes. This + // reflects the way they're most commonly used for lookup. Ideally we'd + // include them, but at query time it's hard to find all the inline + // namespaces to query: the preamble doesn't have a dedicated list. + Policy.SuppressUnwrittenScope = true; + ND.printQualifiedName(OS, Policy); + OS.flush(); + assert(!StringRef(QName).startswith("::")); + return QName; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/AST.h b/clangd/AST.h new file mode 100644 index 000000000..cb47c100b --- /dev/null +++ b/clangd/AST.h @@ -0,0 +1,39 @@ +//===--- AST.h - Utility AST functions -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Various code that examines C++ source code using AST. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_ + +#include "clang/AST/Decl.h" +#include "clang/Basic/SourceLocation.h" + +namespace clang { +class SourceManager; +class Decl; + +namespace clangd { + +/// Find the identifier source location of the given D. +/// +/// The returned location is usually the spelling location where the name of the +/// decl occurs in the code. +SourceLocation findNameLoc(const clang::Decl *D); + +/// Returns the qualified name of ND. The scope doesn't contain unwritten scopes +/// like inline namespaces. +std::string printQualifiedName(const NamedDecl &ND); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H_ diff --git a/clangd/CMakeLists.txt b/clangd/CMakeLists.txt new file mode 100644 index 000000000..33a6e842d --- /dev/null +++ b/clangd/CMakeLists.txt @@ -0,0 +1,72 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +set(CLANGD_ATOMIC_LIB "") +if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB) + list(APPEND CLANGD_ATOMIC_LIB "atomic") +endif() + +add_clang_library(clangDaemon + AST.cpp + ClangdLSPServer.cpp + ClangdServer.cpp + ClangdUnit.cpp + CodeComplete.cpp + CodeCompletionStrings.cpp + Compiler.cpp + Context.cpp + Diagnostics.cpp + DraftStore.cpp + FindSymbols.cpp + FileDistance.cpp + FuzzyMatch.cpp + GlobalCompilationDatabase.cpp + Headers.cpp + JSONRPCDispatcher.cpp + Logger.cpp + Protocol.cpp + ProtocolHandlers.cpp + Quality.cpp + SourceCode.cpp + Threading.cpp + Trace.cpp + TUScheduler.cpp + URI.cpp + XRefs.cpp + + index/CanonicalIncludes.cpp + index/FileIndex.cpp + index/Index.cpp + index/MemIndex.cpp + index/Merge.cpp + index/SymbolCollector.cpp + index/SymbolYAML.cpp + + index/dex/Iterator.cpp + index/dex/Trigram.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangDriver + clangFormat + clangFrontend + clangIndex + clangLex + clangSema + clangSerialization + clangTooling + clangToolingCore + clangToolingInclusions + clangToolingRefactor + ${LLVM_PTHREAD_LIB} + ${CLANGD_ATOMIC_LIB} + ) + +if( LLVM_LIB_FUZZING_ENGINE OR LLVM_USE_SANITIZE_COVERAGE ) + add_subdirectory(fuzzer) +endif() +add_subdirectory(tool) +add_subdirectory(global-symbol-builder) diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp new file mode 100644 index 000000000..7374c6d35 --- /dev/null +++ b/clangd/ClangdLSPServer.cpp @@ -0,0 +1,506 @@ +//===--- 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 "Diagnostics.h" +#include "JSONRPCDispatcher.h" +#include "SourceCode.h" +#include "URI.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" + +using namespace clang::clangd; +using namespace clang; +using namespace llvm; + +namespace { + +/// \brief Supports a test URI scheme with relaxed constraints for lit tests. +/// The path in a test URI will be combined with a platform-specific fake +/// directory to form an absolute path. For example, test:///a.cpp is resolved +/// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix. +class TestScheme : public URIScheme { +public: + llvm::Expected + getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, + llvm::StringRef /*HintPath*/) const override { + using namespace llvm::sys; + // Still require "/" in body to mimic file scheme, as we want lengths of an + // equivalent URI in both schemes to be the same. + if (!Body.startswith("/")) + return llvm::make_error( + "Expect URI body to be an absolute path starting with '/': " + Body, + llvm::inconvertibleErrorCode()); + Body = Body.ltrim('/'); +#ifdef _WIN32 + constexpr char TestDir[] = "C:\\clangd-test"; +#else + constexpr char TestDir[] = "/clangd-test"; +#endif + llvm::SmallVector Path(Body.begin(), Body.end()); + path::native(Path); + auto Err = fs::make_absolute(TestDir, Path); + if (Err) + llvm_unreachable("Failed to make absolute path in test scheme."); + return std::string(Path.begin(), Path.end()); + } + + llvm::Expected + uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { + llvm_unreachable("Clangd must never create a test URI."); + } +}; + +static URISchemeRegistry::Add + X("test", "Test scheme for clangd lit tests."); + +SymbolKindBitset defaultSymbolKinds() { + SymbolKindBitset Defaults; + for (size_t I = SymbolKindMin; I <= static_cast(SymbolKind::Array); + ++I) + Defaults.set(I); + return Defaults; +} + +} // namespace + +void ClangdLSPServer::onInitialize(InitializeParams &Params) { + if (Params.initializationOptions) + applyConfiguration(*Params.initializationOptions); + + if (Params.rootUri && *Params.rootUri) + Server.setRootPath(Params.rootUri->file()); + else if (Params.rootPath && !Params.rootPath->empty()) + Server.setRootPath(*Params.rootPath); + + CCOpts.EnableSnippets = + Params.capabilities.textDocument.completion.completionItem.snippetSupport; + + if (Params.capabilities.workspace && Params.capabilities.workspace->symbol && + Params.capabilities.workspace->symbol->symbolKind) { + for (SymbolKind Kind : + *Params.capabilities.workspace->symbol->symbolKind->valueSet) { + SupportedSymbolKinds.set(static_cast(Kind)); + } + } + + reply(json::Object{ + {{"capabilities", + json::Object{ + {"textDocumentSync", (int)TextDocumentSyncKind::Incremental}, + {"documentFormattingProvider", true}, + {"documentRangeFormattingProvider", true}, + {"documentOnTypeFormattingProvider", + json::Object{ + {"firstTriggerCharacter", "}"}, + {"moreTriggerCharacter", {}}, + }}, + {"codeActionProvider", true}, + {"completionProvider", + json::Object{ + {"resolveProvider", false}, + {"triggerCharacters", {".", ">", ":"}}, + }}, + {"signatureHelpProvider", + json::Object{ + {"triggerCharacters", {"(", ","}}, + }}, + {"definitionProvider", true}, + {"documentHighlightProvider", true}, + {"hoverProvider", true}, + {"renameProvider", true}, + {"documentSymbolProvider", true}, + {"workspaceSymbolProvider", true}, + {"executeCommandProvider", + json::Object{ + {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + }}, + }}}}); +} + +void ClangdLSPServer::onShutdown(ShutdownParams &Params) { + // Do essentially nothing, just say we're ready to exit. + ShutdownRequestReceived = true; + reply(nullptr); +} + +void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; } + +void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) { + PathRef File = Params.textDocument.uri.file(); + if (Params.metadata && !Params.metadata->extraFlags.empty()) { + NonCachedCDB.setExtraFlagsForFile(File, + std::move(Params.metadata->extraFlags)); + CDB.invalidate(File); + } + + std::string &Contents = Params.textDocument.text; + + DraftMgr.addDraft(File, Contents); + Server.addDocument(File, Contents, WantDiagnostics::Yes); +} + +void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) { + auto WantDiags = WantDiagnostics::Auto; + if (Params.wantDiagnostics.hasValue()) + WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes + : WantDiagnostics::No; + + PathRef File = Params.textDocument.uri.file(); + llvm::Expected Contents = + DraftMgr.updateDraft(File, Params.contentChanges); + if (!Contents) { + // If this fails, we are most likely going to be not in sync anymore with + // the client. It is better to remove the draft and let further operations + // fail rather than giving wrong results. + DraftMgr.removeDraft(File); + Server.removeDocument(File); + CDB.invalidate(File); + elog("Failed to update {0}: {1}", File, Contents.takeError()); + return; + } + + Server.addDocument(File, *Contents, WantDiags); +} + +void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) { + Server.onFileEvent(Params); +} + +void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { + auto ApplyEdit = [](WorkspaceEdit WE) { + ApplyWorkspaceEditParams Edit; + Edit.edit = std::move(WE); + // We don't need the response so id == 1 is OK. + // Ideally, we would wait for the response and if there is no error, we + // would reply success/failure to the original RPC. + call("workspace/applyEdit", Edit); + }; + if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND && + Params.workspaceEdit) { + // The flow for "apply-fix" : + // 1. We publish a diagnostic, including fixits + // 2. The user clicks on the diagnostic, the editor asks us for code actions + // 3. We send code actions, with the fixit embedded as context + // 4. The user selects the fixit, the editor asks us to apply it + // 5. We unwrap the changes and send them back to the editor + // 6. The editor applies the changes (applyEdit), and sends us a reply (but + // we ignore it) + + reply("Fix applied."); + ApplyEdit(*Params.workspaceEdit); + } else { + // We should not get here because ExecuteCommandParams would not have + // parsed in the first place and this handler should not be called. But if + // more commands are added, this will be here has a safe guard. + replyError( + ErrorCode::InvalidParams, + llvm::formatv("Unsupported command \"{0}\".", Params.command).str()); + } +} + +void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) { + Server.workspaceSymbols( + Params.query, CCOpts.Limit, + [this](llvm::Expected> Items) { + if (!Items) + return replyError(ErrorCode::InternalError, + llvm::toString(Items.takeError())); + for (auto &Sym : *Items) + Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); + + reply(json::Array(*Items)); + }); +} + +void ClangdLSPServer::onRename(RenameParams &Params) { + Path File = Params.textDocument.uri.file(); + llvm::Optional Code = DraftMgr.getDraft(File); + if (!Code) + return replyError(ErrorCode::InvalidParams, + "onRename called for non-added file"); + + Server.rename( + File, Params.position, Params.newName, + [File, Code, + Params](llvm::Expected> Replacements) { + if (!Replacements) + return replyError(ErrorCode::InternalError, + llvm::toString(Replacements.takeError())); + + // Turn the replacements into the format specified by the Language + // Server Protocol. Fuse them into one big JSON array. + std::vector Edits; + for (const auto &R : *Replacements) + Edits.push_back(replacementToEdit(*Code, R)); + WorkspaceEdit WE; + WE.changes = {{Params.textDocument.uri.uri(), Edits}}; + reply(WE); + }); +} + +void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) { + PathRef File = Params.textDocument.uri.file(); + DraftMgr.removeDraft(File); + Server.removeDocument(File); +} + +void ClangdLSPServer::onDocumentOnTypeFormatting( + DocumentOnTypeFormattingParams &Params) { + auto File = Params.textDocument.uri.file(); + auto Code = DraftMgr.getDraft(File); + if (!Code) + return replyError(ErrorCode::InvalidParams, + "onDocumentOnTypeFormatting called for non-added file"); + + auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position); + if (ReplacementsOrError) + reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); + else + replyError(ErrorCode::UnknownErrorCode, + llvm::toString(ReplacementsOrError.takeError())); +} + +void ClangdLSPServer::onDocumentRangeFormatting( + DocumentRangeFormattingParams &Params) { + auto File = Params.textDocument.uri.file(); + auto Code = DraftMgr.getDraft(File); + if (!Code) + return replyError(ErrorCode::InvalidParams, + "onDocumentRangeFormatting called for non-added file"); + + auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range); + if (ReplacementsOrError) + reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); + else + replyError(ErrorCode::UnknownErrorCode, + llvm::toString(ReplacementsOrError.takeError())); +} + +void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) { + auto File = Params.textDocument.uri.file(); + auto Code = DraftMgr.getDraft(File); + if (!Code) + return replyError(ErrorCode::InvalidParams, + "onDocumentFormatting called for non-added file"); + + auto ReplacementsOrError = Server.formatFile(*Code, File); + if (ReplacementsOrError) + reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get()))); + else + replyError(ErrorCode::UnknownErrorCode, + llvm::toString(ReplacementsOrError.takeError())); +} + +void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) { + Server.documentSymbols( + Params.textDocument.uri.file(), + [this](llvm::Expected> Items) { + if (!Items) + return replyError(ErrorCode::InvalidParams, + llvm::toString(Items.takeError())); + for (auto &Sym : *Items) + Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); + reply(json::Array(*Items)); + }); +} + +void ClangdLSPServer::onCodeAction(CodeActionParams &Params) { + // We provide a code action for each diagnostic at the requested location + // which has FixIts available. + auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); + if (!Code) + return replyError(ErrorCode::InvalidParams, + "onCodeAction called for non-added file"); + + json::Array Commands; + for (Diagnostic &D : Params.context.diagnostics) { + for (auto &F : getFixes(Params.textDocument.uri.file(), D)) { + WorkspaceEdit WE; + std::vector Edits(F.Edits.begin(), F.Edits.end()); + WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}}; + Commands.push_back(json::Object{ + {"title", llvm::formatv("Apply fix: {0}", F.Message)}, + {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}, + {"arguments", {WE}}, + }); + } + } + reply(std::move(Commands)); +} + +void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) { + Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts, + [this](llvm::Expected List) { + if (!List) + return replyError(ErrorCode::InvalidParams, + llvm::toString(List.takeError())); + CompletionList LSPList; + LSPList.isIncomplete = List->HasMore; + for (const auto &R : List->Completions) + LSPList.items.push_back(R.render(CCOpts)); + reply(std::move(LSPList)); + }); +} + +void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) { + Server.signatureHelp(Params.textDocument.uri.file(), Params.position, + [](llvm::Expected SignatureHelp) { + if (!SignatureHelp) + return replyError( + ErrorCode::InvalidParams, + llvm::toString(SignatureHelp.takeError())); + reply(*SignatureHelp); + }); +} + +void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) { + Server.findDefinitions( + Params.textDocument.uri.file(), Params.position, + [](llvm::Expected> Items) { + if (!Items) + return replyError(ErrorCode::InvalidParams, + llvm::toString(Items.takeError())); + reply(json::Array(*Items)); + }); +} + +void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) { + llvm::Optional Result = Server.switchSourceHeader(Params.uri.file()); + reply(Result ? URI::createFile(*Result).toString() : ""); +} + +void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) { + Server.findDocumentHighlights( + Params.textDocument.uri.file(), Params.position, + [](llvm::Expected> Highlights) { + if (!Highlights) + return replyError(ErrorCode::InternalError, + llvm::toString(Highlights.takeError())); + reply(json::Array(*Highlights)); + }); +} + +void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) { + Server.findHover(Params.textDocument.uri.file(), Params.position, + [](llvm::Expected> H) { + if (!H) { + replyError(ErrorCode::InternalError, + llvm::toString(H.takeError())); + return; + } + + reply(*H); + }); +} + +void ClangdLSPServer::applyConfiguration( + const ClangdConfigurationParamsChange &Settings) { + // Compilation database change. + if (Settings.compilationDatabasePath.hasValue()) { + NonCachedCDB.setCompileCommandsDir( + Settings.compilationDatabasePath.getValue()); + CDB.clear(); + + reparseOpenedFiles(); + } +} + +// FIXME: This function needs to be properly tested. +void ClangdLSPServer::onChangeConfiguration( + DidChangeConfigurationParams &Params) { + applyConfiguration(Params.settings); +} + +ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, + const clangd::CodeCompleteOptions &CCOpts, + llvm::Optional CompileCommandsDir, + const ClangdServer::Options &Opts) + : Out(Out), NonCachedCDB(std::move(CompileCommandsDir)), CDB(NonCachedCDB), + CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()), + Server(CDB, FSProvider, /*DiagConsumer=*/*this, Opts) {} + +bool ClangdLSPServer::run(std::FILE *In, JSONStreamStyle InputStyle) { + assert(!IsDone && "Run was called before"); + + // Set up JSONRPCDispatcher. + JSONRPCDispatcher Dispatcher([](const json::Value &Params) { + replyError(ErrorCode::MethodNotFound, "method not found"); + }); + registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this); + + // Run the Language Server loop. + runLanguageServerLoop(In, Out, InputStyle, 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; + + return ShutdownRequestReceived; +} + +std::vector ClangdLSPServer::getFixes(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::onDiagnosticsReady(PathRef File, + std::vector Diagnostics) { + json::Array DiagnosticsJSON; + + DiagnosticToReplacementMap LocalFixIts; // Temporary storage + for (auto &Diag : Diagnostics) { + toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef Fixes) { + DiagnosticsJSON.push_back(json::Object{ + {"range", Diag.range}, + {"severity", Diag.severity}, + {"message", Diag.message}, + }); + + auto &FixItsForDiagnostic = LocalFixIts[Diag]; + std::copy(Fixes.begin(), Fixes.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. + Out.writeMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"method", "textDocument/publishDiagnostics"}, + {"params", + json::Object{ + {"uri", URIForFile{File}}, + {"diagnostics", std::move(DiagnosticsJSON)}, + }}, + }); +} + +void ClangdLSPServer::reparseOpenedFiles() { + for (const Path &FilePath : DraftMgr.getActiveFiles()) + Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath), + WantDiagnostics::Auto); +} diff --git a/clangd/ClangdLSPServer.h b/clangd/ClangdLSPServer.h new file mode 100644 index 000000000..644e7628c --- /dev/null +++ b/clangd/ClangdLSPServer.h @@ -0,0 +1,126 @@ +//===--- 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 "DraftStore.h" +#include "FindSymbols.h" +#include "GlobalCompilationDatabase.h" +#include "Path.h" +#include "Protocol.h" +#include "ProtocolHandlers.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/Optional.h" + +namespace clang { +namespace clangd { + +class JSONOutput; +class SymbolIndex; + +/// This class provides implementation of an LSP server, glueing the JSON +/// dispatch and ClangdServer together. +class ClangdLSPServer : private DiagnosticsConsumer, private ProtocolCallbacks { +public: + /// If \p CompileCommandsDir has a value, compile_commands.json will be + /// loaded only from \p CompileCommandsDir. Otherwise, clangd will look + /// for compile_commands.json in all parent directories of each file. + ClangdLSPServer(JSONOutput &Out, const clangd::CodeCompleteOptions &CCOpts, + llvm::Optional CompileCommandsDir, + const ClangdServer::Options &Opts); + + /// 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. + /// + /// \return Whether we received a 'shutdown' request before an 'exit' request. + bool run(std::FILE *In, + JSONStreamStyle InputStyle = JSONStreamStyle::Standard); + +private: + // Implement DiagnosticsConsumer. + void onDiagnosticsReady(PathRef File, std::vector Diagnostics) override; + + // Implement ProtocolCallbacks. + void onInitialize(InitializeParams &Params) override; + void onShutdown(ShutdownParams &Params) override; + void onExit(ExitParams &Params) override; + void onDocumentDidOpen(DidOpenTextDocumentParams &Params) override; + void onDocumentDidChange(DidChangeTextDocumentParams &Params) override; + void onDocumentDidClose(DidCloseTextDocumentParams &Params) override; + void + onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params) override; + void + onDocumentRangeFormatting(DocumentRangeFormattingParams &Params) override; + void onDocumentFormatting(DocumentFormattingParams &Params) override; + void onDocumentSymbol(DocumentSymbolParams &Params) override; + void onCodeAction(CodeActionParams &Params) override; + void onCompletion(TextDocumentPositionParams &Params) override; + void onSignatureHelp(TextDocumentPositionParams &Params) override; + void onGoToDefinition(TextDocumentPositionParams &Params) override; + void onSwitchSourceHeader(TextDocumentIdentifier &Params) override; + void onDocumentHighlight(TextDocumentPositionParams &Params) override; + void onFileEvent(DidChangeWatchedFilesParams &Params) override; + void onCommand(ExecuteCommandParams &Params) override; + void onWorkspaceSymbol(WorkspaceSymbolParams &Params) override; + void onRename(RenameParams &Parames) override; + void onHover(TextDocumentPositionParams &Params) override; + void onChangeConfiguration(DidChangeConfigurationParams &Params) override; + + std::vector getFixes(StringRef File, const clangd::Diagnostic &D); + + /// Forces a reparse of all currently opened files. As a result, this method + /// may be very expensive. This method is normally called when the + /// compilation database is changed. + void reparseOpenedFiles(); + void applyConfiguration(const ClangdConfigurationParamsChange &Settings); + + JSONOutput &Out; + /// Used to indicate that the 'shutdown' request was received from the + /// Language Server client. + bool ShutdownRequestReceived = false; + + /// Used to indicate that the 'exit' notification 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, LSPDiagnosticCompare> + DiagnosticToReplacementMap; + /// Caches FixIts per file and diagnostics + llvm::StringMap FixItsMap; + + // Various ClangdServer parameters go here. It's important they're created + // before ClangdServer. + DirectoryBasedGlobalCompilationDatabase NonCachedCDB; + CachingCompilationDb CDB; + + RealFileSystemProvider FSProvider; + /// Options used for code completion + clangd::CodeCompleteOptions CCOpts; + /// The supported kinds of the client. + SymbolKindBitset SupportedSymbolKinds; + + // Store of the current versions of the open documents. + DraftStore DraftMgr; + + // 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..01cc82394 --- /dev/null +++ b/clangd/ClangdServer.cpp @@ -0,0 +1,475 @@ +//===--- 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 "CodeComplete.h" +#include "FindSymbols.h" +#include "Headers.h" +#include "SourceCode.h" +#include "XRefs.h" +#include "index/Merge.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang; +using namespace clang::clangd; + +namespace { + +void ignoreError(llvm::Error Err) { + handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {}); +} + +std::string getStandardResourceDir() { + static int Dummy; // Just an address in this process. + return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); +} + +class RefactoringResultCollector final + : public tooling::RefactoringResultConsumer { +public: + void handleError(llvm::Error Err) override { + assert(!Result.hasValue()); + // FIXME: figure out a way to return better message for DiagnosticError. + // clangd uses llvm::toString to convert the Err to string, however, for + // DiagnosticError, only "clang diagnostic" will be generated. + Result = std::move(Err); + } + + // Using the handle(SymbolOccurrences) from parent class. + using tooling::RefactoringResultConsumer::handle; + + void handle(tooling::AtomicChanges SourceReplacements) override { + assert(!Result.hasValue()); + Result = std::move(SourceReplacements); + } + + Optional> Result; +}; + +} // namespace + +ClangdServer::Options ClangdServer::optsForTest() { + ClangdServer::Options Opts; + Opts.UpdateDebounce = std::chrono::steady_clock::duration::zero(); // Faster! + Opts.StorePreamblesInMemory = true; + Opts.AsyncThreadsCount = 4; // Consistent! + return Opts; +} + +ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB, + FileSystemProvider &FSProvider, + DiagnosticsConsumer &DiagConsumer, + const Options &Opts) + : CDB(CDB), DiagConsumer(DiagConsumer), FSProvider(FSProvider), + ResourceDir(Opts.ResourceDir ? Opts.ResourceDir->str() + : getStandardResourceDir()), + FileIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex(Opts.URISchemes) + : nullptr), + PCHs(std::make_shared()), + // Pass a callback into `WorkScheduler` to extract symbols from a newly + // parsed file and rebuild the file index synchronously each time an AST + // is parsed. + // FIXME(ioeric): this can be slow and we may be able to index on less + // critical paths. + WorkScheduler( + Opts.AsyncThreadsCount, Opts.StorePreamblesInMemory, + FileIdx + ? [this](PathRef Path, ASTContext &AST, + std::shared_ptr + PP) { FileIdx->update(Path, &AST, std::move(PP)); } + : PreambleParsedCallback(), + Opts.UpdateDebounce, Opts.RetentionPolicy) { + if (FileIdx && Opts.StaticIndex) { + MergedIndex = mergeIndex(FileIdx.get(), Opts.StaticIndex); + Index = MergedIndex.get(); + } else if (FileIdx) + Index = FileIdx.get(); + else if (Opts.StaticIndex) + Index = Opts.StaticIndex; + else + Index = nullptr; +} + +void ClangdServer::setRootPath(PathRef RootPath) { + auto FS = FSProvider.getFileSystem(); + auto Status = FS->status(RootPath); + if (!Status) + elog("Failed to get status for RootPath {0}: {1}", RootPath, + Status.getError().message()); + else if (Status->isDirectory()) + this->RootPath = RootPath; + else + elog("The provided RootPath {0} is not a directory.", RootPath); +} + +void ClangdServer::addDocument(PathRef File, StringRef Contents, + WantDiagnostics WantDiags) { + DocVersion Version = ++InternalVersion[File]; + ParseInputs Inputs = {getCompileCommand(File), FSProvider.getFileSystem(), + Contents.str()}; + + Path FileStr = File.str(); + WorkScheduler.update(File, std::move(Inputs), WantDiags, + [this, FileStr, Version](std::vector Diags) { + consumeDiagnostics(FileStr, Version, std::move(Diags)); + }); +} + +void ClangdServer::removeDocument(PathRef File) { + ++InternalVersion[File]; + WorkScheduler.remove(File); +} + +void ClangdServer::codeComplete(PathRef File, Position Pos, + const clangd::CodeCompleteOptions &Opts, + Callback CB) { + // Copy completion options for passing them to async task handler. + auto CodeCompleteOpts = Opts; + if (!CodeCompleteOpts.Index) // Respect overridden index. + CodeCompleteOpts.Index = Index; + + // Copy PCHs to avoid accessing this->PCHs concurrently + std::shared_ptr PCHs = this->PCHs; + auto FS = FSProvider.getFileSystem(); + auto Task = [PCHs, Pos, FS, + CodeCompleteOpts](Path File, Callback CB, + llvm::Expected IP) { + if (!IP) + return CB(IP.takeError()); + + auto PreambleData = IP->Preamble; + + // FIXME(ibiryukov): even if Preamble is non-null, we may want to check + // both the old and the new version in case only one of them matches. + CodeCompleteResult Result = clangd::codeComplete( + File, IP->Command, PreambleData ? &PreambleData->Preamble : nullptr, + PreambleData ? PreambleData->Includes : IncludeStructure(), + IP->Contents, Pos, FS, PCHs, CodeCompleteOpts); + CB(std::move(Result)); + }; + + WorkScheduler.runWithPreamble("CodeComplete", File, + Bind(Task, File.str(), std::move(CB))); +} + +void ClangdServer::signatureHelp(PathRef File, Position Pos, + Callback CB) { + + auto PCHs = this->PCHs; + auto FS = FSProvider.getFileSystem(); + auto Action = [Pos, FS, PCHs](Path File, Callback CB, + llvm::Expected IP) { + if (!IP) + return CB(IP.takeError()); + + auto PreambleData = IP->Preamble; + CB(clangd::signatureHelp(File, IP->Command, + PreambleData ? &PreambleData->Preamble : nullptr, + IP->Contents, Pos, FS, PCHs)); + }; + + WorkScheduler.runWithPreamble("SignatureHelp", File, + Bind(Action, File.str(), std::move(CB))); +} + +llvm::Expected +ClangdServer::formatRange(StringRef Code, PathRef File, Range Rng) { + llvm::Expected Begin = positionToOffset(Code, Rng.start); + if (!Begin) + return Begin.takeError(); + llvm::Expected End = positionToOffset(Code, Rng.end); + if (!End) + return End.takeError(); + return formatCode(Code, File, {tooling::Range(*Begin, *End - *Begin)}); +} + +llvm::Expected ClangdServer::formatFile(StringRef Code, + PathRef File) { + // Format everything. + return formatCode(Code, File, {tooling::Range(0, Code.size())}); +} + +llvm::Expected +ClangdServer::formatOnType(StringRef Code, PathRef File, Position Pos) { + // Look for the previous opening brace from the character position and + // format starting from there. + llvm::Expected CursorPos = positionToOffset(Code, Pos); + if (!CursorPos) + return CursorPos.takeError(); + size_t PreviousLBracePos = StringRef(Code).find_last_of('{', *CursorPos); + if (PreviousLBracePos == StringRef::npos) + PreviousLBracePos = *CursorPos; + size_t Len = *CursorPos - PreviousLBracePos; + + return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)}); +} + +void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName, + Callback> CB) { + auto Action = [Pos](Path File, std::string NewName, + Callback> CB, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + auto &AST = InpAST->AST; + + RefactoringResultCollector ResultCollector; + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + SourceLocation SourceLocationBeg = + clangd::getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + tooling::RefactoringRuleContext Context( + AST.getASTContext().getSourceManager()); + Context.setASTContext(AST.getASTContext()); + auto Rename = clang::tooling::RenameOccurrences::initiate( + Context, SourceRange(SourceLocationBeg), NewName); + if (!Rename) + return CB(Rename.takeError()); + + Rename->invoke(ResultCollector, Context); + + assert(ResultCollector.Result.hasValue()); + if (!ResultCollector.Result.getValue()) + return CB(ResultCollector.Result->takeError()); + + std::vector Replacements; + for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) { + tooling::Replacements ChangeReps = Change.getReplacements(); + for (const auto &Rep : ChangeReps) { + // FIXME: Right now we only support renaming the main file, so we + // drop replacements not for the main file. In the future, we might + // consider to support: + // * rename in any included header + // * rename only in the "main" header + // * provide an error if there are symbols we won't rename (e.g. + // std::vector) + // * rename globally in project + // * rename in open files + if (Rep.getFilePath() == File) + Replacements.push_back(Rep); + } + } + return CB(std::move(Replacements)); + }; + + WorkScheduler.runWithAST( + "Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB))); +} + +void ClangdServer::dumpAST(PathRef File, + llvm::unique_function Callback) { + auto Action = [](decltype(Callback) Callback, + llvm::Expected InpAST) { + if (!InpAST) { + ignoreError(InpAST.takeError()); + return Callback(""); + } + std::string Result; + + llvm::raw_string_ostream ResultOS(Result); + clangd::dumpAST(InpAST->AST, ResultOS); + ResultOS.flush(); + + Callback(Result); + }; + + WorkScheduler.runWithAST("DumpAST", File, Bind(Action, std::move(Callback))); +} + +void ClangdServer::findDefinitions(PathRef File, Position Pos, + Callback> CB) { + auto Action = [Pos, this](Callback> CB, + llvm::Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::findDefinitions(InpAST->AST, Pos, Index)); + }; + + WorkScheduler.runWithAST("Definitions", File, Bind(Action, std::move(CB))); +} + +llvm::Optional ClangdServer::switchSourceHeader(PathRef Path) { + + StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx", + ".c++", ".m", ".mm"}; + StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"}; + + StringRef PathExt = llvm::sys::path::extension(Path); + + // Lookup in a list of known extensions. + auto SourceIter = + std::find_if(std::begin(SourceExtensions), std::end(SourceExtensions), + [&PathExt](PathRef SourceExt) { + return SourceExt.equals_lower(PathExt); + }); + bool IsSource = SourceIter != std::end(SourceExtensions); + + auto HeaderIter = + std::find_if(std::begin(HeaderExtensions), std::end(HeaderExtensions), + [&PathExt](PathRef HeaderExt) { + return HeaderExt.equals_lower(PathExt); + }); + + bool IsHeader = HeaderIter != std::end(HeaderExtensions); + + // We can only switch between the known extensions. + if (!IsSource && !IsHeader) + return llvm::None; + + // Array to lookup extensions for the switch. An opposite of where original + // extension was found. + ArrayRef NewExts; + if (IsSource) + NewExts = HeaderExtensions; + else + NewExts = SourceExtensions; + + // Storage for the new path. + SmallString<128> NewPath = StringRef(Path); + + // Instance of vfs::FileSystem, used for file existence checks. + auto FS = FSProvider.getFileSystem(); + + // Loop through switched extension candidates. + for (StringRef NewExt : NewExts) { + llvm::sys::path::replace_extension(NewPath, NewExt); + if (FS->exists(NewPath)) + return NewPath.str().str(); // First str() to convert from SmallString to + // StringRef, second to convert from StringRef + // to std::string + + // Also check NewExt in upper-case, just in case. + llvm::sys::path::replace_extension(NewPath, NewExt.upper()); + if (FS->exists(NewPath)) + return NewPath.str().str(); + } + + return llvm::None; +} + +llvm::Expected +ClangdServer::formatCode(llvm::StringRef Code, PathRef File, + ArrayRef Ranges) { + // Call clang-format. + auto FS = FSProvider.getFileSystem(); + auto Style = format::getStyle(format::DefaultFormatStyle, File, + format::DefaultFallbackStyle, Code, FS.get()); + if (!Style) + return Style.takeError(); + + tooling::Replacements IncludeReplaces = + format::sortIncludes(*Style, Code, Ranges, File); + auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces); + if (!Changed) + return Changed.takeError(); + + return IncludeReplaces.merge(format::reformat( + Style.get(), *Changed, + tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges), + File)); +} + +void ClangdServer::findDocumentHighlights( + PathRef File, Position Pos, Callback> CB) { + auto Action = [Pos](Callback> CB, + llvm::Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::findDocumentHighlights(InpAST->AST, Pos)); + }; + + WorkScheduler.runWithAST("Highlights", File, Bind(Action, std::move(CB))); +} + +void ClangdServer::findHover(PathRef File, Position Pos, + Callback> CB) { + auto Action = [Pos](Callback> CB, + llvm::Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getHover(InpAST->AST, Pos)); + }; + + WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); +} + +void ClangdServer::consumeDiagnostics(PathRef File, DocVersion Version, + std::vector Diags) { + // We need to serialize access to resulting diagnostics to avoid calling + // `onDiagnosticsReady` in the wrong order. + std::lock_guard DiagsLock(DiagnosticsMutex); + DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[File]; + + // FIXME(ibiryukov): get rid of '<' comparison here. In the current + // implementation diagnostics will not be reported after version counters' + // overflow. This should not happen in practice, since DocVersion is a + // 64-bit unsigned integer. + if (Version < LastReportedDiagsVersion) + return; + LastReportedDiagsVersion = Version; + + DiagConsumer.onDiagnosticsReady(File, std::move(Diags)); +} + +tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) { + llvm::Optional C = CDB.getCompileCommand(File); + if (!C) // FIXME: Suppress diagnostics? Let the user know? + C = CDB.getFallbackCommand(File); + + // Inject the resource dir. + // FIXME: Don't overwrite it if it's already there. + C->CommandLine.push_back("-resource-dir=" + ResourceDir); + return std::move(*C); +} + +void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { + // FIXME: Do nothing for now. This will be used for indexing and potentially + // invalidating other caches. +} + +void ClangdServer::workspaceSymbols( + StringRef Query, int Limit, Callback> CB) { + CB(clangd::getWorkspaceSymbols(Query, Limit, Index, + RootPath ? *RootPath : "")); +} + +void ClangdServer::documentSymbols( + StringRef File, Callback> CB) { + auto Action = [](Callback> CB, + llvm::Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getDocumentSymbols(InpAST->AST)); + }; + WorkScheduler.runWithAST("documentSymbols", File, + Bind(Action, std::move(CB))); +} + +std::vector> +ClangdServer::getUsedBytesPerFile() const { + return WorkScheduler.getUsedBytesPerFile(); +} + +LLVM_NODISCARD bool +ClangdServer::blockUntilIdleForTest(llvm::Optional TimeoutSeconds) { + return WorkScheduler.blockUntilIdle(timeoutSeconds(TimeoutSeconds)); +} diff --git a/clangd/ClangdServer.h b/clangd/ClangdServer.h new file mode 100644 index 000000000..745f8c307 --- /dev/null +++ b/clangd/ClangdServer.h @@ -0,0 +1,246 @@ +//===--- 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 "ClangdUnit.h" +#include "CodeComplete.h" +#include "FSProvider.h" +#include "Function.h" +#include "GlobalCompilationDatabase.h" +#include "Protocol.h" +#include "TUScheduler.h" +#include "index/FileIndex.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 +#include +#include +#include +#include + +namespace clang { +class PCHContainerOperations; + +namespace clangd { + +class DiagnosticsConsumer { +public: + virtual ~DiagnosticsConsumer() = default; + + /// Called by ClangdServer when \p Diagnostics for \p File are ready. + virtual void onDiagnosticsReady(PathRef File, + std::vector Diagnostics) = 0; +}; + +/// Provides API to manage ASTs for a collection of C++ files and request +/// various language features. +/// Currently supports async diagnostics, code completion, formatting and goto +/// definition. +class ClangdServer { +public: + struct Options { + /// To process requests asynchronously, ClangdServer spawns worker threads. + /// If 0, all requests are processed on the calling thread. + unsigned AsyncThreadsCount = getDefaultAsyncThreadsCount(); + + /// AST caching policy. The default is to keep up to 3 ASTs in memory. + ASTRetentionPolicy RetentionPolicy; + + /// Cached preambles are potentially large. If false, store them on disk. + bool StorePreamblesInMemory = true; + + /// If true, ClangdServer builds a dynamic in-memory index for symbols in + /// opened files and uses the index to augment code completion results. + bool BuildDynamicSymbolIndex = false; + + /// URI schemes to use when building the dynamic index. + /// If empty, the default schemes in SymbolCollector will be used. + std::vector URISchemes; + + /// If set, use this index to augment code completion results. + SymbolIndex *StaticIndex = nullptr; + + /// The resource directory is used to find internal headers, overriding + /// defaults and -resource-dir compiler flag). + /// If None, ClangdServer calls CompilerInvocation::GetResourcePath() to + /// obtain the standard resource directory. + llvm::Optional ResourceDir = llvm::None; + + /// Time to wait after a new file version before computing diagnostics. + std::chrono::steady_clock::duration UpdateDebounce = + std::chrono::milliseconds(500); + }; + // Sensible default options for use in tests. + // Features like indexing must be enabled if desired. + static Options optsForTest(); + + /// Creates a new ClangdServer instance. + /// + /// ClangdServer uses \p CDB to obtain compilation arguments for parsing. Note + /// that ClangdServer only obtains compilation arguments once for each newly + /// added file (i.e., when processing a first call to addDocument) and reuses + /// those arguments for subsequent reparses. However, ClangdServer will check + /// if compilation arguments changed on calls to forceReparse(). + /// + /// After each parsing request finishes, ClangdServer reports diagnostics to + /// \p DiagConsumer. Note that a callback to \p DiagConsumer happens on a + /// worker thread. Therefore, instances of \p DiagConsumer must properly + /// synchronize access to shared state. + ClangdServer(GlobalCompilationDatabase &CDB, FileSystemProvider &FSProvider, + DiagnosticsConsumer &DiagConsumer, const Options &Opts); + + /// Set the root path of the workspace. + void setRootPath(PathRef RootPath); + + /// 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, + WantDiagnostics WD = WantDiagnostics::Auto); + + /// Remove \p File from list of tracked files, schedule a request to free + /// resources associated with it. + void removeDocument(PathRef File); + + /// Run code completion for \p File at \p Pos. + /// Request is processed asynchronously. + /// + /// This method should only be called for currently tracked files. However, it + /// is safe to call removeDocument for \p File after this method returns, even + /// while returned future is not yet ready. + /// A version of `codeComplete` that runs \p Callback on the processing thread + /// when codeComplete results become available. + void codeComplete(PathRef File, Position Pos, + const clangd::CodeCompleteOptions &Opts, + Callback CB); + + /// Provide signature help for \p File at \p Pos. This method should only be + /// called for tracked files. + void signatureHelp(PathRef File, Position Pos, Callback CB); + + /// Get definition of symbol at a specified \p Line and \p Column in \p File. + void findDefinitions(PathRef File, Position Pos, + Callback> CB); + + /// Helper function that returns a path to the corresponding source file when + /// given a header file and vice versa. + llvm::Optional switchSourceHeader(PathRef Path); + + /// Get document highlights for a given position. + void findDocumentHighlights(PathRef File, Position Pos, + Callback> CB); + + /// Get code hover for a given position. + void findHover(PathRef File, Position Pos, + Callback> CB); + + /// Retrieve the top symbols from the workspace matching a query. + void workspaceSymbols(StringRef Query, int Limit, + Callback> CB); + + /// Retrieve the symbols within the specified file. + void documentSymbols(StringRef File, + Callback> CB); + + /// Run formatting for \p Rng inside \p File with content \p Code. + llvm::Expected formatRange(StringRef Code, + PathRef File, Range Rng); + + /// Run formatting for the whole \p File with content \p Code. + llvm::Expected formatFile(StringRef Code, + PathRef File); + + /// Run formatting after a character was typed at \p Pos in \p File with + /// content \p Code. + llvm::Expected + formatOnType(StringRef Code, PathRef File, Position Pos); + + /// Rename all occurrences of the symbol at the \p Pos in \p File to + /// \p NewName. + void rename(PathRef File, Position Pos, llvm::StringRef NewName, + Callback> CB); + + /// 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. + void dumpAST(PathRef File, llvm::unique_function Callback); + /// Called when an event occurs for a watched file in the workspace. + void onFileEvent(const DidChangeWatchedFilesParams &Params); + + /// Returns estimated memory usage for each of the currently open files. + /// The order of results is unspecified. + /// Overall memory usage of clangd may be significantly more than reported + /// here, as this metric does not account (at least) for: + /// - memory occupied by static and dynamic index, + /// - memory required for in-flight requests, + /// FIXME: those metrics might be useful too, we should add them. + std::vector> getUsedBytesPerFile() const; + + // Blocks the main thread until the server is idle. Only for use in tests. + // Returns false if the timeout expires. + LLVM_NODISCARD bool + blockUntilIdleForTest(llvm::Optional TimeoutSeconds = 10); + +private: + /// FIXME: This stats several files to find a .clang-format file. I/O can be + /// slow. Think of a way to cache this. + llvm::Expected + formatCode(llvm::StringRef Code, PathRef File, + ArrayRef Ranges); + + typedef uint64_t DocVersion; + + void consumeDiagnostics(PathRef File, DocVersion Version, + std::vector Diags); + + tooling::CompileCommand getCompileCommand(PathRef File); + + GlobalCompilationDatabase &CDB; + DiagnosticsConsumer &DiagConsumer; + FileSystemProvider &FSProvider; + + /// Used to synchronize diagnostic responses for added and removed files. + llvm::StringMap InternalVersion; + + Path ResourceDir; + // The index used to look up symbols. This could be: + // - null (all index functionality is optional) + // - the dynamic index owned by ClangdServer (FileIdx) + // - the static index passed to the constructor + // - a merged view of a static and dynamic index (MergedIndex) + SymbolIndex *Index; + // If present, an up-to-date of symbols in open files. Read via Index. + std::unique_ptr FileIdx; + // If present, a merged view of FileIdx and an external index. Read via Index. + std::unique_ptr MergedIndex; + // If set, this represents the workspace path. + llvm::Optional RootPath; + std::shared_ptr PCHs; + /// Used to serialize diagnostic callbacks. + /// FIXME(ibiryukov): get rid of an extra map and put all version counters + /// into CppFile. + std::mutex DiagnosticsMutex; + /// Maps from a filename to the latest version of reported diagnostics. + llvm::StringMap ReportedDiagnosticVersions; + // 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. + TUScheduler WorkScheduler; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/ClangdUnit.cpp b/clangd/ClangdUnit.cpp new file mode 100644 index 000000000..86e7497a3 --- /dev/null +++ b/clangd/ClangdUnit.cpp @@ -0,0 +1,407 @@ +//===--- 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 "Compiler.h" +#include "Diagnostics.h" +#include "Logger.h" +#include "SourceCode.h" +#include "Trace.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/Utils.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/MacroInfo.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Sema/Sema.h" +#include "clang/Serialization/ASTWriter.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/CrashRecoveryContext.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang::clangd; +using namespace clang; + +namespace { + +bool compileCommandsAreEqual(const tooling::CompileCommand &LHS, + const tooling::CompileCommand &RHS) { + // We don't check for Output, it should not matter to clangd. + return LHS.Directory == RHS.Directory && LHS.Filename == RHS.Filename && + llvm::makeArrayRef(LHS.CommandLine).equals(RHS.CommandLine); +} + +template std::size_t getUsedBytes(const std::vector &Vec) { + return Vec.capacity() * sizeof(T); +} + +class DeclTrackingASTConsumer : public ASTConsumer { +public: + DeclTrackingASTConsumer(std::vector &TopLevelDecls) + : TopLevelDecls(TopLevelDecls) {} + + bool HandleTopLevelDecl(DeclGroupRef DG) override { + for (Decl *D : DG) { + // ObjCMethodDecl are not actually top-level decls. + if (isa(D)) + continue; + + TopLevelDecls.push_back(D); + } + return true; + } + +private: + std::vector &TopLevelDecls; +}; + +class ClangdFrontendAction : public SyntaxOnlyAction { +public: + std::vector takeTopLevelDecls() { return std::move(TopLevelDecls); } + +protected: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + return llvm::make_unique(/*ref*/ TopLevelDecls); + } + +private: + std::vector TopLevelDecls; +}; + +class CppFilePreambleCallbacks : public PreambleCallbacks { +public: + CppFilePreambleCallbacks(PathRef File, PreambleParsedCallback ParsedCallback) + : File(File), ParsedCallback(ParsedCallback) {} + + IncludeStructure takeIncludes() { return std::move(Includes); } + + void AfterExecute(CompilerInstance &CI) override { + if (!ParsedCallback) + return; + trace::Span Tracer("Running PreambleCallback"); + ParsedCallback(File, CI.getASTContext(), CI.getPreprocessorPtr()); + } + + void BeforeExecute(CompilerInstance &CI) override { + SourceMgr = &CI.getSourceManager(); + } + + std::unique_ptr createPPCallbacks() override { + assert(SourceMgr && "SourceMgr must be set at this point"); + return collectIncludeStructureCallback(*SourceMgr, &Includes); + } + +private: + PathRef File; + PreambleParsedCallback ParsedCallback; + IncludeStructure Includes; + SourceManager *SourceMgr = nullptr; +}; + +} // namespace + +void clangd::dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) { + AST.getASTContext().getTranslationUnitDecl()->dump(OS, true); +} + +llvm::Optional +ParsedAST::build(std::unique_ptr CI, + std::shared_ptr Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS) { + assert(CI); + // Command-line parsing sets DisableFree to true by default, but we don't want + // to leak memory in clangd. + CI->getFrontendOpts().DisableFree = false; + const PrecompiledPreamble *PreamblePCH = + Preamble ? &Preamble->Preamble : nullptr; + + StoreDiags ASTDiags; + auto Clang = + prepareCompilerInstance(std::move(CI), PreamblePCH, std::move(Buffer), + std::move(PCHs), std::move(VFS), ASTDiags); + if (!Clang) + return llvm::None; + + // Recover resources if we crash before exiting this method. + llvm::CrashRecoveryContextCleanupRegistrar CICleanup( + Clang.get()); + + auto Action = llvm::make_unique(); + const FrontendInputFile &MainInput = Clang->getFrontendOpts().Inputs[0]; + if (!Action->BeginSourceFile(*Clang, MainInput)) { + log("BeginSourceFile() failed when building AST for {0}", + MainInput.getFile()); + return llvm::None; + } + + // Copy over the includes from the preamble, then combine with the + // non-preamble includes below. + auto Includes = Preamble ? Preamble->Includes : IncludeStructure{}; + Clang->getPreprocessor().addPPCallbacks( + collectIncludeStructureCallback(Clang->getSourceManager(), &Includes)); + + if (!Action->Execute()) + log("Execute() failed when building AST for {0}", MainInput.getFile()); + + // UnitDiagsConsumer is local, we can not store it in CompilerInstance that + // has a longer lifetime. + Clang->getDiagnostics().setClient(new IgnoreDiagnostics); + // CompilerInstance won't run this callback, do it directly. + ASTDiags.EndSourceFile(); + + std::vector ParsedDecls = Action->takeTopLevelDecls(); + std::vector Diags = ASTDiags.take(); + // Add diagnostics from the preamble, if any. + if (Preamble) + Diags.insert(Diags.begin(), Preamble->Diags.begin(), Preamble->Diags.end()); + return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action), + std::move(ParsedDecls), std::move(Diags), + std::move(Includes)); +} + +ParsedAST::ParsedAST(ParsedAST &&Other) = default; + +ParsedAST &ParsedAST::operator=(ParsedAST &&Other) = default; + +ParsedAST::~ParsedAST() { + if (Action) { + Action->EndSourceFile(); + } +} + +ASTContext &ParsedAST::getASTContext() { return Clang->getASTContext(); } + +const ASTContext &ParsedAST::getASTContext() const { + return Clang->getASTContext(); +} + +Preprocessor &ParsedAST::getPreprocessor() { return Clang->getPreprocessor(); } + +std::shared_ptr ParsedAST::getPreprocessorPtr() { + return Clang->getPreprocessorPtr(); +} + +const Preprocessor &ParsedAST::getPreprocessor() const { + return Clang->getPreprocessor(); +} + +ArrayRef ParsedAST::getLocalTopLevelDecls() { + return LocalTopLevelDecls; +} + +const std::vector &ParsedAST::getDiagnostics() const { return Diags; } + +std::size_t ParsedAST::getUsedBytes() const { + auto &AST = getASTContext(); + // FIXME(ibiryukov): we do not account for the dynamically allocated part of + // Message and Fixes inside each diagnostic. + std::size_t Total = + ::getUsedBytes(LocalTopLevelDecls) + ::getUsedBytes(Diags); + + // FIXME: the rest of the function is almost a direct copy-paste from + // libclang's clang_getCXTUResourceUsage. We could share the implementation. + + // Sum up variaous allocators inside the ast context and the preprocessor. + Total += AST.getASTAllocatedMemory(); + Total += AST.getSideTableAllocatedMemory(); + Total += AST.Idents.getAllocator().getTotalMemory(); + Total += AST.Selectors.getTotalMemory(); + + Total += AST.getSourceManager().getContentCacheSize(); + Total += AST.getSourceManager().getDataStructureSizes(); + Total += AST.getSourceManager().getMemoryBufferSizes().malloc_bytes; + + if (ExternalASTSource *Ext = AST.getExternalSource()) + Total += Ext->getMemoryBufferSizes().malloc_bytes; + + const Preprocessor &PP = getPreprocessor(); + Total += PP.getTotalMemory(); + if (PreprocessingRecord *PRec = PP.getPreprocessingRecord()) + Total += PRec->getTotalMemory(); + Total += PP.getHeaderSearchInfo().getTotalMemory(); + + return Total; +} + +const IncludeStructure &ParsedAST::getIncludeStructure() const { + return Includes; +} + +PreambleData::PreambleData(PrecompiledPreamble Preamble, + std::vector Diags, IncludeStructure Includes) + : Preamble(std::move(Preamble)), Diags(std::move(Diags)), + Includes(std::move(Includes)) {} + +ParsedAST::ParsedAST(std::shared_ptr Preamble, + std::unique_ptr Clang, + std::unique_ptr Action, + std::vector LocalTopLevelDecls, + std::vector Diags, IncludeStructure Includes) + : Preamble(std::move(Preamble)), Clang(std::move(Clang)), + Action(std::move(Action)), Diags(std::move(Diags)), + LocalTopLevelDecls(std::move(LocalTopLevelDecls)), + Includes(std::move(Includes)) { + assert(this->Clang); + assert(this->Action); +} + +std::unique_ptr +clangd::buildCompilerInvocation(const ParseInputs &Inputs) { + std::vector ArgStrs; + for (const auto &S : Inputs.CompileCommand.CommandLine) + ArgStrs.push_back(S.c_str()); + + if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { + log("Couldn't set working directory when creating compiler invocation."); + // We proceed anyway, our lit-tests rely on results for non-existing working + // dirs. + } + + // FIXME(ibiryukov): store diagnostics from CommandLine when we start + // reporting them. + IgnoreDiagnostics IgnoreDiagnostics; + IntrusiveRefCntPtr CommandLineDiagsEngine = + CompilerInstance::createDiagnostics(new DiagnosticOptions, + &IgnoreDiagnostics, false); + std::unique_ptr CI = createInvocationFromCommandLine( + ArgStrs, CommandLineDiagsEngine, Inputs.FS); + if (!CI) + return nullptr; + // createInvocationFromCommandLine sets DisableFree. + CI->getFrontendOpts().DisableFree = false; + CI->getLangOpts()->CommentOpts.ParseAllComments = true; + return CI; +} + +std::shared_ptr clangd::buildPreamble( + PathRef FileName, CompilerInvocation &CI, + std::shared_ptr OldPreamble, + const tooling::CompileCommand &OldCompileCommand, const ParseInputs &Inputs, + std::shared_ptr PCHs, bool StoreInMemory, + PreambleParsedCallback PreambleCallback) { + // Note that we don't need to copy the input contents, preamble can live + // without those. + auto ContentsBuffer = llvm::MemoryBuffer::getMemBuffer(Inputs.Contents); + auto Bounds = + ComputePreambleBounds(*CI.getLangOpts(), ContentsBuffer.get(), 0); + + if (OldPreamble && + compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) && + OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds, + Inputs.FS.get())) { + vlog("Reusing preamble for file {0}", Twine(FileName)); + return OldPreamble; + } + vlog("Preamble for file {0} cannot be reused. Attempting to rebuild it.", + FileName); + + trace::Span Tracer("BuildPreamble"); + SPAN_ATTACH(Tracer, "File", FileName); + StoreDiags PreambleDiagnostics; + IntrusiveRefCntPtr PreambleDiagsEngine = + CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), + &PreambleDiagnostics, false); + + // Skip function bodies when building the preamble to speed up building + // the preamble and make it smaller. + assert(!CI.getFrontendOpts().SkipFunctionBodies); + CI.getFrontendOpts().SkipFunctionBodies = true; + // We don't want to write comment locations into PCH. They are racy and slow + // to read back. We rely on dynamic index for the comments instead. + CI.getPreprocessorOpts().WriteCommentListToPCH = false; + + CppFilePreambleCallbacks SerializedDeclsCollector(FileName, PreambleCallback); + if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { + log("Couldn't set working directory when building the preamble."); + // We proceed anyway, our lit-tests rely on results for non-existing working + // dirs. + } + auto BuiltPreamble = PrecompiledPreamble::Build( + CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, Inputs.FS, PCHs, + StoreInMemory, SerializedDeclsCollector); + + // When building the AST for the main file, we do want the function + // bodies. + CI.getFrontendOpts().SkipFunctionBodies = false; + + if (BuiltPreamble) { + vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(), + FileName); + return std::make_shared( + std::move(*BuiltPreamble), PreambleDiagnostics.take(), + SerializedDeclsCollector.takeIncludes()); + } else { + elog("Could not build a preamble for file {0}", FileName); + return nullptr; + } +} + +llvm::Optional clangd::buildAST( + PathRef FileName, std::unique_ptr Invocation, + const ParseInputs &Inputs, std::shared_ptr Preamble, + std::shared_ptr PCHs) { + trace::Span Tracer("BuildAST"); + SPAN_ATTACH(Tracer, "File", FileName); + + if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { + log("Couldn't set working directory when building the preamble."); + // We proceed anyway, our lit-tests rely on results for non-existing working + // dirs. + } + + return ParsedAST::build( + llvm::make_unique(*Invocation), Preamble, + llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents), PCHs, Inputs.FS); +} + +SourceLocation clangd::getBeginningOfIdentifier(ParsedAST &Unit, + const Position &Pos, + const FileID FID) { + const ASTContext &AST = Unit.getASTContext(); + const SourceManager &SourceMgr = AST.getSourceManager(); + auto Offset = positionToOffset(SourceMgr.getBufferData(FID), Pos); + if (!Offset) { + log("getBeginningOfIdentifier: {0}", Offset.takeError()); + return SourceLocation(); + } + SourceLocation InputLoc = SourceMgr.getComposedLoc(FID, *Offset); + + // GetBeginningOfToken(pos) is almost what we want, but does the wrong thing + // if the cursor is at the end of the identifier. + // Instead, we lex at GetBeginningOfToken(pos - 1). The cases are: + // 1) at the beginning of an identifier, we'll be looking at something + // that isn't an identifier. + // 2) at the middle or end of an identifier, we get the identifier. + // 3) anywhere outside an identifier, we'll get some non-identifier thing. + // We can't actually distinguish cases 1 and 3, but returning the original + // location is correct for both! + if (*Offset == 0) // Case 1 or 3. + return SourceMgr.getMacroArgExpandedLocation(InputLoc); + SourceLocation Before = + SourceMgr.getMacroArgExpandedLocation(InputLoc.getLocWithOffset(-1)); + Before = Lexer::GetBeginningOfToken(Before, SourceMgr, AST.getLangOpts()); + Token Tok; + if (Before.isValid() && + !Lexer::getRawToken(Before, Tok, SourceMgr, AST.getLangOpts(), false) && + Tok.is(tok::raw_identifier)) + return Before; // Case 2. + return SourceMgr.getMacroArgExpandedLocation(InputLoc); // Case 1 or 3. +} diff --git a/clangd/ClangdUnit.h b/clangd/ClangdUnit.h new file mode 100644 index 000000000..c7aca17ea --- /dev/null +++ b/clangd/ClangdUnit.h @@ -0,0 +1,171 @@ +//===--- 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 "Diagnostics.h" +#include "Function.h" +#include "Headers.h" +#include "Path.h" +#include "Protocol.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/PrecompiledPreamble.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Serialization/ASTBitCodes.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Core/Replacement.h" +#include +#include +#include + +namespace llvm { +class raw_ostream; +} + +namespace clang { +class PCHContainerOperations; + +namespace vfs { +class FileSystem; +} + +namespace tooling { +struct CompileCommand; +} + +namespace clangd { + +// Stores Preamble and associated data. +struct PreambleData { + PreambleData(PrecompiledPreamble Preamble, std::vector Diags, + IncludeStructure Includes); + + tooling::CompileCommand CompileCommand; + PrecompiledPreamble Preamble; + std::vector Diags; + // Processes like code completions and go-to-definitions will need #include + // information, and their compile action skips preamble range. + IncludeStructure Includes; +}; + +/// Information required to run clang, e.g. to parse AST or do code completion. +struct ParseInputs { + tooling::CompileCommand CompileCommand; + IntrusiveRefCntPtr FS; + std::string Contents; +}; + +/// Stores and provides access to parsed AST. +class ParsedAST { +public: + /// Attempts to run Clang and store parsed AST. If \p Preamble is non-null + /// it is reused during parsing. + static llvm::Optional + build(std::unique_ptr CI, + std::shared_ptr Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS); + + ParsedAST(ParsedAST &&Other); + ParsedAST &operator=(ParsedAST &&Other); + + ~ParsedAST(); + + /// Note that the returned ast will not contain decls from the preamble that + /// were not deserialized during parsing. Clients should expect only decls + /// from the main file to be in the AST. + ASTContext &getASTContext(); + const ASTContext &getASTContext() const; + + Preprocessor &getPreprocessor(); + std::shared_ptr getPreprocessorPtr(); + const Preprocessor &getPreprocessor() const; + + /// This function returns top-level decls present in the main file of the AST. + /// The result does not include the decls that come from the preamble. + /// (These should be const, but RecursiveASTVisitor requires Decl*). + ArrayRef getLocalTopLevelDecls(); + + const std::vector &getDiagnostics() const; + + /// Returns the esitmated size of the AST and the accessory structures, in + /// bytes. Does not include the size of the preamble. + std::size_t getUsedBytes() const; + const IncludeStructure &getIncludeStructure() const; + +private: + ParsedAST(std::shared_ptr Preamble, + std::unique_ptr Clang, + std::unique_ptr Action, + std::vector LocalTopLevelDecls, std::vector Diags, + IncludeStructure Includes); + + // In-memory preambles must outlive the AST, it is important that this member + // goes before Clang and Action. + std::shared_ptr Preamble; + // We store an "incomplete" FrontendAction (i.e. no EndSourceFile was called + // on it) and CompilerInstance used to run it. That way we don't have to do + // complex memory management of all Clang structures on our own. (They are + // stored in CompilerInstance and cleaned up by + // FrontendAction.EndSourceFile). + std::unique_ptr Clang; + std::unique_ptr Action; + + // Data, stored after parsing. + std::vector Diags; + // Top-level decls inside the current file. Not that this does not include + // top-level decls from the preamble. + std::vector LocalTopLevelDecls; + IncludeStructure Includes; +}; + +using PreambleParsedCallback = std::function)>; + +/// Builds compiler invocation that could be used to build AST or preamble. +std::unique_ptr +buildCompilerInvocation(const ParseInputs &Inputs); + +/// Rebuild the preamble for the new inputs unless the old one can be reused. +/// If \p OldPreamble can be reused, it is returned unchanged. +/// If \p OldPreamble is null, always builds the preamble. +/// If \p PreambleCallback is set, it will be run on top of the AST while +/// building the preamble. Note that if the old preamble was reused, no AST is +/// built and, therefore, the callback will not be executed. +std::shared_ptr +buildPreamble(PathRef FileName, CompilerInvocation &CI, + std::shared_ptr OldPreamble, + const tooling::CompileCommand &OldCompileCommand, + const ParseInputs &Inputs, + std::shared_ptr PCHs, bool StoreInMemory, + PreambleParsedCallback PreambleCallback); + +/// Build an AST from provided user inputs. This function does not check if +/// preamble can be reused, as this function expects that \p Preamble is the +/// result of calling buildPreamble. +llvm::Optional +buildAST(PathRef FileName, std::unique_ptr Invocation, + const ParseInputs &Inputs, + std::shared_ptr Preamble, + std::shared_ptr PCHs); + +/// Get the beginning SourceLocation at a specified \p Pos. +/// May be invalid if Pos is, or if there's no identifier. +SourceLocation getBeginningOfIdentifier(ParsedAST &Unit, const Position &Pos, + const FileID FID); + +/// For testing/debugging purposes. Note that this method deserializes all +/// unserialized Decls, so use with care. +void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS); + +} // namespace clangd +} // namespace clang +#endif diff --git a/clangd/CodeComplete.cpp b/clangd/CodeComplete.cpp new file mode 100644 index 000000000..a0406591f --- /dev/null +++ b/clangd/CodeComplete.cpp @@ -0,0 +1,1303 @@ +//===--- CodeComplete.cpp ---------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Code completion has several moving parts: +// - AST-based completions are provided using the completion hooks in Sema. +// - external completions are retrieved from the index (using hints from Sema) +// - the two sources overlap, and must be merged and overloads bundled +// - results must be scored and ranked (see Quality.h) before rendering +// +// Signature help works in a similar way as code completion, but it is simpler: +// it's purely AST-based, and there are few candidates. +// +//===---------------------------------------------------------------------===// + +#include "CodeComplete.h" +#include "AST.h" +#include "CodeCompletionStrings.h" +#include "Compiler.h" +#include "FileDistance.h" +#include "FuzzyMatch.h" +#include "Headers.h" +#include "Logger.h" +#include "Quality.h" +#include "SourceCode.h" +#include "Trace.h" +#include "URI.h" +#include "index/Index.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/Sema.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/ScopedPrinter.h" +#include + +// We log detailed candidate here if you run with -debug-only=codecomplete. +#define DEBUG_TYPE "CodeComplete" + +namespace clang { +namespace clangd { +namespace { + +CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) { + using SK = index::SymbolKind; + switch (Kind) { + case SK::Unknown: + return CompletionItemKind::Missing; + case SK::Module: + case SK::Namespace: + case SK::NamespaceAlias: + return CompletionItemKind::Module; + case SK::Macro: + return CompletionItemKind::Text; + case SK::Enum: + return CompletionItemKind::Enum; + // FIXME(ioeric): use LSP struct instead of class when it is suppoted in the + // protocol. + case SK::Struct: + case SK::Class: + case SK::Protocol: + case SK::Extension: + case SK::Union: + return CompletionItemKind::Class; + // FIXME(ioeric): figure out whether reference is the right type for aliases. + case SK::TypeAlias: + case SK::Using: + return CompletionItemKind::Reference; + case SK::Function: + // FIXME(ioeric): this should probably be an operator. This should be fixed + // when `Operator` is support type in the protocol. + case SK::ConversionFunction: + return CompletionItemKind::Function; + case SK::Variable: + case SK::Parameter: + return CompletionItemKind::Variable; + case SK::Field: + return CompletionItemKind::Field; + // FIXME(ioeric): use LSP enum constant when it is supported in the protocol. + case SK::EnumConstant: + return CompletionItemKind::Value; + case SK::InstanceMethod: + case SK::ClassMethod: + case SK::StaticMethod: + case SK::Destructor: + return CompletionItemKind::Method; + case SK::InstanceProperty: + case SK::ClassProperty: + case SK::StaticProperty: + return CompletionItemKind::Property; + case SK::Constructor: + return CompletionItemKind::Constructor; + } + llvm_unreachable("Unhandled clang::index::SymbolKind."); +} + +CompletionItemKind +toCompletionItemKind(CodeCompletionResult::ResultKind ResKind, + const NamedDecl *Decl) { + if (Decl) + return toCompletionItemKind(index::getSymbolInfo(Decl).Kind); + switch (ResKind) { + case CodeCompletionResult::RK_Declaration: + llvm_unreachable("RK_Declaration without Decl"); + case CodeCompletionResult::RK_Keyword: + return CompletionItemKind::Keyword; + case CodeCompletionResult::RK_Macro: + return CompletionItemKind::Text; // unfortunately, there's no 'Macro' + // completion items in LSP. + case CodeCompletionResult::RK_Pattern: + return CompletionItemKind::Snippet; + } + llvm_unreachable("Unhandled CodeCompletionResult::ResultKind."); +} + +/// Get the optional chunk as a string. This function is possibly recursive. +/// +/// The parameter info for each parameter is appended to the Parameters. +std::string +getOptionalParameters(const CodeCompletionString &CCS, + std::vector &Parameters) { + std::string Result; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Optional: + assert(Chunk.Optional && + "Expected the optional code completion string to be non-null."); + Result += getOptionalParameters(*Chunk.Optional, Parameters); + break; + case CodeCompletionString::CK_VerticalSpace: + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + Result += Chunk.Text; + ParameterInformation Info; + Info.label = Chunk.Text; + Parameters.push_back(std::move(Info)); + break; + } + default: + Result += Chunk.Text; + break; + } + } + return Result; +} + +/// Creates a `HeaderFile` from \p Header which can be either a URI or a literal +/// include. +static llvm::Expected toHeaderFile(StringRef Header, + llvm::StringRef HintPath) { + if (isLiteralInclude(Header)) + return HeaderFile{Header.str(), /*Verbatim=*/true}; + auto U = URI::parse(Header); + if (!U) + return U.takeError(); + + auto IncludePath = URI::includeSpelling(*U); + if (!IncludePath) + return IncludePath.takeError(); + if (!IncludePath->empty()) + return HeaderFile{std::move(*IncludePath), /*Verbatim=*/true}; + + auto Resolved = URI::resolve(*U, HintPath); + if (!Resolved) + return Resolved.takeError(); + return HeaderFile{std::move(*Resolved), /*Verbatim=*/false}; +} + +/// A code completion result, in clang-native form. +/// It may be promoted to a CompletionItem if it's among the top-ranked results. +struct CompletionCandidate { + llvm::StringRef Name; // Used for filtering and sorting. + // We may have a result from Sema, from the index, or both. + const CodeCompletionResult *SemaResult = nullptr; + const Symbol *IndexResult = nullptr; + + // Returns a token identifying the overload set this is part of. + // 0 indicates it's not part of any overload set. + size_t overloadSet() const { + SmallString<256> Scratch; + if (IndexResult) { + switch (IndexResult->SymInfo.Kind) { + case index::SymbolKind::ClassMethod: + case index::SymbolKind::InstanceMethod: + case index::SymbolKind::StaticMethod: + assert(false && "Don't expect members from index in code completion"); + // fall through + case index::SymbolKind::Function: + // We can't group overloads together that need different #includes. + // This could break #include insertion. + return hash_combine( + (IndexResult->Scope + IndexResult->Name).toStringRef(Scratch), + headerToInsertIfNotPresent().getValueOr("")); + default: + return 0; + } + } + assert(SemaResult); + // We need to make sure we're consistent with the IndexResult case! + const NamedDecl *D = SemaResult->Declaration; + if (!D || !D->isFunctionOrFunctionTemplate()) + return 0; + { + llvm::raw_svector_ostream OS(Scratch); + D->printQualifiedName(OS); + } + return hash_combine(Scratch, headerToInsertIfNotPresent().getValueOr("")); + } + + llvm::Optional headerToInsertIfNotPresent() const { + if (!IndexResult || !IndexResult->Detail || + IndexResult->Detail->IncludeHeader.empty()) + return llvm::None; + if (SemaResult && SemaResult->Declaration) { + // Avoid inserting new #include if the declaration is found in the current + // file e.g. the symbol is forward declared. + auto &SM = SemaResult->Declaration->getASTContext().getSourceManager(); + for (const Decl *RD : SemaResult->Declaration->redecls()) + if (SM.isInMainFile(SM.getExpansionLoc(RD->getLocStart()))) + return llvm::None; + } + return IndexResult->Detail->IncludeHeader; + } + + using Bundle = llvm::SmallVector; +}; +using ScoredBundle = + std::pair; +struct ScoredBundleGreater { + bool operator()(const ScoredBundle &L, const ScoredBundle &R) { + if (L.second.Total != R.second.Total) + return L.second.Total > R.second.Total; + return L.first.front().Name < + R.first.front().Name; // Earlier name is better. + } +}; + +// Assembles a code completion out of a bundle of >=1 completion candidates. +// Many of the expensive strings are only computed at this point, once we know +// the candidate bundle is going to be returned. +// +// Many fields are the same for all candidates in a bundle (e.g. name), and are +// computed from the first candidate, in the constructor. +// Others vary per candidate, so add() must be called for remaining candidates. +struct CodeCompletionBuilder { + CodeCompletionBuilder(ASTContext &ASTCtx, const CompletionCandidate &C, + CodeCompletionString *SemaCCS, + const IncludeInserter &Includes, StringRef FileName, + const CodeCompleteOptions &Opts) + : ASTCtx(ASTCtx), ExtractDocumentation(Opts.IncludeComments) { + add(C, SemaCCS); + if (C.SemaResult) { + Completion.Origin |= SymbolOrigin::AST; + Completion.Name = llvm::StringRef(SemaCCS->getTypedText()); + if (Completion.Scope.empty()) { + if ((C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) || + (C.SemaResult->Kind == CodeCompletionResult::RK_Pattern)) + if (const auto *D = C.SemaResult->getDeclaration()) + if (const auto *ND = llvm::dyn_cast(D)) + Completion.Scope = + splitQualifiedName(printQualifiedName(*ND)).first; + } + Completion.Kind = + toCompletionItemKind(C.SemaResult->Kind, C.SemaResult->Declaration); + } + if (C.IndexResult) { + Completion.Origin |= C.IndexResult->Origin; + if (Completion.Scope.empty()) + Completion.Scope = C.IndexResult->Scope; + if (Completion.Kind == CompletionItemKind::Missing) + Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind); + if (Completion.Name.empty()) + Completion.Name = C.IndexResult->Name; + } + if (auto Inserted = C.headerToInsertIfNotPresent()) { + // Turn absolute path into a literal string that can be #included. + auto Include = [&]() -> Expected> { + auto ResolvedDeclaring = + toHeaderFile(C.IndexResult->CanonicalDeclaration.FileURI, FileName); + if (!ResolvedDeclaring) + return ResolvedDeclaring.takeError(); + auto ResolvedInserted = toHeaderFile(*Inserted, FileName); + if (!ResolvedInserted) + return ResolvedInserted.takeError(); + return std::make_pair(Includes.calculateIncludePath(*ResolvedDeclaring, + *ResolvedInserted), + Includes.shouldInsertInclude(*ResolvedDeclaring, + *ResolvedInserted)); + }(); + if (Include) { + Completion.Header = Include->first; + if (Include->second) + Completion.HeaderInsertion = Includes.insert(Include->first); + } else + log("Failed to generate include insertion edits for adding header " + "(FileURI='{0}', IncludeHeader='{1}') into {2}", + C.IndexResult->CanonicalDeclaration.FileURI, + C.IndexResult->Detail->IncludeHeader, FileName); + } + } + + void add(const CompletionCandidate &C, CodeCompletionString *SemaCCS) { + assert(bool(C.SemaResult) == bool(SemaCCS)); + Bundled.emplace_back(); + BundledEntry &S = Bundled.back(); + if (C.SemaResult) { + getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, + &Completion.RequiredQualifier); + S.ReturnType = getReturnType(*SemaCCS); + } else if (C.IndexResult) { + S.Signature = C.IndexResult->Signature; + S.SnippetSuffix = C.IndexResult->CompletionSnippetSuffix; + if (auto *D = C.IndexResult->Detail) + S.ReturnType = D->ReturnType; + } + if (ExtractDocumentation && Completion.Documentation.empty()) { + if (C.IndexResult && C.IndexResult->Detail) + Completion.Documentation = C.IndexResult->Detail->Documentation; + else if (C.SemaResult) + Completion.Documentation = getDocComment(ASTCtx, *C.SemaResult, + /*CommentsFromHeader=*/false); + } + } + + CodeCompletion build() { + Completion.ReturnType = summarizeReturnType(); + Completion.Signature = summarizeSignature(); + Completion.SnippetSuffix = summarizeSnippet(); + Completion.BundleSize = Bundled.size(); + return std::move(Completion); + } + +private: + struct BundledEntry { + std::string SnippetSuffix; + std::string Signature; + std::string ReturnType; + }; + + // If all BundledEntrys have the same value for a property, return it. + template + const std::string *onlyValue() const { + auto B = Bundled.begin(), E = Bundled.end(); + for (auto I = B + 1; I != E; ++I) + if (I->*Member != B->*Member) + return nullptr; + return &(B->*Member); + } + + std::string summarizeReturnType() const { + if (auto *RT = onlyValue<&BundledEntry::ReturnType>()) + return *RT; + return ""; + } + + std::string summarizeSnippet() const { + if (auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>()) + return *Snippet; + // All bundles are function calls. + return "(${0})"; + } + + std::string summarizeSignature() const { + if (auto *Signature = onlyValue<&BundledEntry::Signature>()) + return *Signature; + // All bundles are function calls. + return "(…)"; + } + + ASTContext &ASTCtx; + CodeCompletion Completion; + SmallVector Bundled; + bool ExtractDocumentation; +}; + +// Determine the symbol ID for a Sema code completion result, if possible. +llvm::Optional getSymbolID(const CodeCompletionResult &R) { + switch (R.Kind) { + case CodeCompletionResult::RK_Declaration: + case CodeCompletionResult::RK_Pattern: { + llvm::SmallString<128> USR; + if (/*Ignore=*/clang::index::generateUSRForDecl(R.Declaration, USR)) + return None; + return SymbolID(USR); + } + case CodeCompletionResult::RK_Macro: + // FIXME: Macros do have USRs, but the CCR doesn't contain enough info. + case CodeCompletionResult::RK_Keyword: + return None; + } + llvm_unreachable("unknown CodeCompletionResult kind"); +} + +// Scopes of the paritial identifier we're trying to complete. +// It is used when we query the index for more completion results. +struct SpecifiedScope { + // The scopes we should look in, determined by Sema. + // + // If the qualifier was fully resolved, we look for completions in these + // scopes; if there is an unresolved part of the qualifier, it should be + // resolved within these scopes. + // + // Examples of qualified completion: + // + // "::vec" => {""} + // "using namespace std; ::vec^" => {"", "std::"} + // "namespace ns {using namespace std;} ns::^" => {"ns::", "std::"} + // "std::vec^" => {""} // "std" unresolved + // + // Examples of unqualified completion: + // + // "vec^" => {""} + // "using namespace std; vec^" => {"", "std::"} + // "using namespace std; namespace ns { vec^ }" => {"ns::", "std::", ""} + // + // "" for global namespace, "ns::" for normal namespace. + std::vector AccessibleScopes; + // The full scope qualifier as typed by the user (without the leading "::"). + // Set if the qualifier is not fully resolved by Sema. + llvm::Optional UnresolvedQualifier; + + // Construct scopes being queried in indexes. + // This method format the scopes to match the index request representation. + std::vector scopesForIndexQuery() { + std::vector Results; + for (llvm::StringRef AS : AccessibleScopes) { + Results.push_back(AS); + if (UnresolvedQualifier) + Results.back() += *UnresolvedQualifier; + } + return Results; + } +}; + +// Get all scopes that will be queried in indexes. +std::vector getQueryScopes(CodeCompletionContext &CCContext, + const SourceManager &SM) { + auto GetAllAccessibleScopes = [](CodeCompletionContext &CCContext) { + SpecifiedScope Info; + for (auto *Context : CCContext.getVisitedContexts()) { + if (isa(Context)) + Info.AccessibleScopes.push_back(""); // global namespace + else if (const auto *NS = dyn_cast(Context)) + Info.AccessibleScopes.push_back(NS->getQualifiedNameAsString() + "::"); + } + return Info; + }; + + auto SS = CCContext.getCXXScopeSpecifier(); + + // Unqualified completion (e.g. "vec^"). + if (!SS) { + // FIXME: Once we can insert namespace qualifiers and use the in-scope + // namespaces for scoring, search in all namespaces. + // FIXME: Capture scopes and use for scoring, for example, + // "using namespace std; namespace foo {v^}" => + // foo::value > std::vector > boost::variant + return GetAllAccessibleScopes(CCContext).scopesForIndexQuery(); + } + + // Qualified completion ("std::vec^"), we have two cases depending on whether + // the qualifier can be resolved by Sema. + if ((*SS)->isValid()) { // Resolved qualifier. + return GetAllAccessibleScopes(CCContext).scopesForIndexQuery(); + } + + // Unresolved qualifier. + // FIXME: When Sema can resolve part of a scope chain (e.g. + // "known::unknown::id"), we should expand the known part ("known::") rather + // than treating the whole thing as unknown. + SpecifiedScope Info; + Info.AccessibleScopes.push_back(""); // global namespace + + Info.UnresolvedQualifier = + Lexer::getSourceText(CharSourceRange::getCharRange((*SS)->getRange()), SM, + clang::LangOptions()) + .ltrim("::"); + // Sema excludes the trailing "::". + if (!Info.UnresolvedQualifier->empty()) + *Info.UnresolvedQualifier += "::"; + + return Info.scopesForIndexQuery(); +} + +// Should we perform index-based completion in a context of the specified kind? +// FIXME: consider allowing completion, but restricting the result types. +bool contextAllowsIndex(enum CodeCompletionContext::Kind K) { + switch (K) { + case CodeCompletionContext::CCC_TopLevel: + case CodeCompletionContext::CCC_ObjCInterface: + case CodeCompletionContext::CCC_ObjCImplementation: + case CodeCompletionContext::CCC_ObjCIvarList: + case CodeCompletionContext::CCC_ClassStructUnion: + case CodeCompletionContext::CCC_Statement: + case CodeCompletionContext::CCC_Expression: + case CodeCompletionContext::CCC_ObjCMessageReceiver: + case CodeCompletionContext::CCC_EnumTag: + case CodeCompletionContext::CCC_UnionTag: + case CodeCompletionContext::CCC_ClassOrStructTag: + case CodeCompletionContext::CCC_ObjCProtocolName: + case CodeCompletionContext::CCC_Namespace: + case CodeCompletionContext::CCC_Type: + case CodeCompletionContext::CCC_Name: // FIXME: why does ns::^ give this? + case CodeCompletionContext::CCC_PotentiallyQualifiedName: + case CodeCompletionContext::CCC_ParenthesizedExpression: + case CodeCompletionContext::CCC_ObjCInterfaceName: + case CodeCompletionContext::CCC_ObjCCategoryName: + return true; + case CodeCompletionContext::CCC_Other: // Be conservative. + case CodeCompletionContext::CCC_OtherWithMacros: + case CodeCompletionContext::CCC_DotMemberAccess: + case CodeCompletionContext::CCC_ArrowMemberAccess: + case CodeCompletionContext::CCC_ObjCPropertyAccess: + case CodeCompletionContext::CCC_MacroName: + case CodeCompletionContext::CCC_MacroNameUse: + case CodeCompletionContext::CCC_PreprocessorExpression: + case CodeCompletionContext::CCC_PreprocessorDirective: + case CodeCompletionContext::CCC_NaturalLanguage: + case CodeCompletionContext::CCC_SelectorName: + case CodeCompletionContext::CCC_TypeQualifiers: + case CodeCompletionContext::CCC_ObjCInstanceMessage: + case CodeCompletionContext::CCC_ObjCClassMessage: + case CodeCompletionContext::CCC_Recovery: + return false; + } + llvm_unreachable("unknown code completion context"); +} + +// Some member calls are blacklisted because they're so rarely useful. +static bool isBlacklistedMember(const NamedDecl &D) { + // Destructor completion is rarely useful, and works inconsistently. + // (s.^ completes ~string, but s.~st^ is an error). + if (D.getKind() == Decl::CXXDestructor) + return true; + // Injected name may be useful for A::foo(), but who writes A::A::foo()? + if (auto *R = dyn_cast_or_null(&D)) + if (R->isInjectedClassName()) + return true; + // Explicit calls to operators are also rare. + auto NameKind = D.getDeclName().getNameKind(); + if (NameKind == DeclarationName::CXXOperatorName || + NameKind == DeclarationName::CXXLiteralOperatorName || + NameKind == DeclarationName::CXXConversionFunctionName) + return true; + return false; +} + +// The CompletionRecorder captures Sema code-complete output, including context. +// It filters out ignored results (but doesn't apply fuzzy-filtering yet). +// It doesn't do scoring or conversion to CompletionItem yet, as we want to +// merge with index results first. +// Generally the fields and methods of this object should only be used from +// within the callback. +struct CompletionRecorder : public CodeCompleteConsumer { + CompletionRecorder(const CodeCompleteOptions &Opts, + llvm::unique_function ResultsCallback) + : CodeCompleteConsumer(Opts.getClangCompleteOpts(), + /*OutputIsBinary=*/false), + CCContext(CodeCompletionContext::CCC_Other), Opts(Opts), + CCAllocator(std::make_shared()), + CCTUInfo(CCAllocator), ResultsCallback(std::move(ResultsCallback)) { + assert(this->ResultsCallback); + } + + std::vector Results; + CodeCompletionContext CCContext; + Sema *CCSema = nullptr; // Sema that created the results. + // FIXME: Sema is scary. Can we store ASTContext and Preprocessor, instead? + + void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context, + CodeCompletionResult *InResults, + unsigned NumResults) override final { + // Results from recovery mode are generally useless, and the callback after + // recovery (if any) is usually more interesting. To make sure we handle the + // future callback from sema, we just ignore all callbacks in recovery mode, + // as taking only results from recovery mode results in poor completion + // results. + // FIXME: in case there is no future sema completion callback after the + // recovery mode, we might still want to provide some results (e.g. trivial + // identifier-based completion). + if (Context.getKind() == CodeCompletionContext::CCC_Recovery) { + log("Code complete: Ignoring sema code complete callback with Recovery " + "context."); + return; + } + // If a callback is called without any sema result and the context does not + // support index-based completion, we simply skip it to give way to + // potential future callbacks with results. + if (NumResults == 0 && !contextAllowsIndex(Context.getKind())) + return; + if (CCSema) { + log("Multiple code complete callbacks (parser backtracked?). " + "Dropping results from context {0}, keeping results from {1}.", + getCompletionKindString(Context.getKind()), + getCompletionKindString(this->CCContext.getKind())); + return; + } + // Record the completion context. + CCSema = &S; + CCContext = Context; + + // Retain the results we might want. + for (unsigned I = 0; I < NumResults; ++I) { + auto &Result = InResults[I]; + // Drop hidden items which cannot be found by lookup after completion. + // Exception: some items can be named by using a qualifier. + if (Result.Hidden && (!Result.Qualifier || Result.QualifierIsInformative)) + continue; + if (!Opts.IncludeIneligibleResults && + (Result.Availability == CXAvailability_NotAvailable || + Result.Availability == CXAvailability_NotAccessible)) + continue; + if (Result.Declaration && + !Context.getBaseType().isNull() // is this a member-access context? + && isBlacklistedMember(*Result.Declaration)) + continue; + // We choose to never append '::' to completion results in clangd. + Result.StartsNestedNameSpecifier = false; + Results.push_back(Result); + } + ResultsCallback(); + } + + CodeCompletionAllocator &getAllocator() override { return *CCAllocator; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + + // Returns the filtering/sorting name for Result, which must be from Results. + // Returned string is owned by this recorder (or the AST). + llvm::StringRef getName(const CodeCompletionResult &Result) { + switch (Result.Kind) { + case CodeCompletionResult::RK_Declaration: + if (auto *ID = Result.Declaration->getIdentifier()) + return ID->getName(); + break; + case CodeCompletionResult::RK_Keyword: + return Result.Keyword; + case CodeCompletionResult::RK_Macro: + return Result.Macro->getName(); + case CodeCompletionResult::RK_Pattern: + return Result.Pattern->getTypedText(); + } + auto *CCS = codeCompletionString(Result); + return CCS->getTypedText(); + } + + // Build a CodeCompletion string for R, which must be from Results. + // The CCS will be owned by this recorder. + CodeCompletionString *codeCompletionString(const CodeCompletionResult &R) { + // CodeCompletionResult doesn't seem to be const-correct. We own it, anyway. + return const_cast(R).CreateCodeCompletionString( + *CCSema, CCContext, *CCAllocator, CCTUInfo, + /*IncludeBriefComments=*/false); + } + +private: + CodeCompleteOptions Opts; + std::shared_ptr CCAllocator; + CodeCompletionTUInfo CCTUInfo; + llvm::unique_function ResultsCallback; +}; + +class SignatureHelpCollector final : public CodeCompleteConsumer { + +public: + SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts, + SignatureHelp &SigHelp) + : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), + SigHelp(SigHelp), + Allocator(std::make_shared()), + CCTUInfo(Allocator) {} + + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates) override { + SigHelp.signatures.reserve(NumCandidates); + // FIXME(rwols): How can we determine the "active overload candidate"? + // Right now the overloaded candidates seem to be provided in a "best fit" + // order, so I'm not too worried about this. + SigHelp.activeSignature = 0; + assert(CurrentArg <= (unsigned)std::numeric_limits::max() && + "too many arguments"); + SigHelp.activeParameter = static_cast(CurrentArg); + for (unsigned I = 0; I < NumCandidates; ++I) { + const auto &Candidate = Candidates[I]; + const auto *CCS = Candidate.CreateSignatureString( + CurrentArg, S, *Allocator, CCTUInfo, true); + assert(CCS && "Expected the CodeCompletionString to be non-null"); + // FIXME: for headers, we need to get a comment from the index. + SigHelp.signatures.push_back(processOverloadCandidate( + Candidate, *CCS, + getParameterDocComment(S.getASTContext(), Candidate, CurrentArg, + /*CommentsFromHeaders=*/false))); + } + } + + GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; } + + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } + +private: + // FIXME(ioeric): consider moving CodeCompletionString logic here to + // CompletionString.h. + SignatureInformation + processOverloadCandidate(const OverloadCandidate &Candidate, + const CodeCompletionString &CCS, + llvm::StringRef DocComment) const { + SignatureInformation Result; + const char *ReturnType = nullptr; + + Result.documentation = formatDocumentation(CCS, DocComment); + + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_ResultType: + // A piece of text that describes the type of an entity or, + // for functions and methods, the return type. + assert(!ReturnType && "Unexpected CK_ResultType"); + ReturnType = Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + Result.label += Chunk.Text; + ParameterInformation Info; + Info.label = Chunk.Text; + Result.parameters.push_back(std::move(Info)); + break; + } + case CodeCompletionString::CK_Optional: { + // The rest of the parameters are defaulted/optional. + assert(Chunk.Optional && + "Expected the optional code completion string to be non-null."); + Result.label += + getOptionalParameters(*Chunk.Optional, Result.parameters); + break; + } + case CodeCompletionString::CK_VerticalSpace: + break; + default: + Result.label += Chunk.Text; + break; + } + } + if (ReturnType) { + Result.label += " -> "; + Result.label += ReturnType; + } + return Result; + } + + SignatureHelp &SigHelp; + std::shared_ptr Allocator; + CodeCompletionTUInfo CCTUInfo; + +}; // SignatureHelpCollector + +struct SemaCompleteInput { + PathRef FileName; + const tooling::CompileCommand &Command; + PrecompiledPreamble const *Preamble; + StringRef Contents; + Position Pos; + IntrusiveRefCntPtr VFS; + std::shared_ptr PCHs; +}; + +// Invokes Sema code completion on a file. +// If \p Includes is set, it will be updated based on the compiler invocation. +bool semaCodeComplete(std::unique_ptr Consumer, + const clang::CodeCompleteOptions &Options, + const SemaCompleteInput &Input, + IncludeStructure *Includes = nullptr) { + trace::Span Tracer("Sema completion"); + std::vector ArgStrs; + for (const auto &S : Input.Command.CommandLine) + ArgStrs.push_back(S.c_str()); + + if (Input.VFS->setCurrentWorkingDirectory(Input.Command.Directory)) { + log("Couldn't set working directory"); + // We run parsing anyway, our lit-tests rely on results for non-existing + // working dirs. + } + + IgnoreDiagnostics DummyDiagsConsumer; + auto CI = createInvocationFromCommandLine( + ArgStrs, + CompilerInstance::createDiagnostics(new DiagnosticOptions, + &DummyDiagsConsumer, false), + Input.VFS); + if (!CI) { + elog("Couldn't create CompilerInvocation"); + return false; + } + auto &FrontendOpts = CI->getFrontendOpts(); + FrontendOpts.DisableFree = false; + FrontendOpts.SkipFunctionBodies = true; + CI->getLangOpts()->CommentOpts.ParseAllComments = true; + // Disable typo correction in Sema. + CI->getLangOpts()->SpellChecking = false; + // Setup code completion. + FrontendOpts.CodeCompleteOpts = Options; + FrontendOpts.CodeCompletionAt.FileName = Input.FileName; + auto Offset = positionToOffset(Input.Contents, Input.Pos); + if (!Offset) { + elog("Code completion position was invalid {0}", Offset.takeError()); + return false; + } + std::tie(FrontendOpts.CodeCompletionAt.Line, + FrontendOpts.CodeCompletionAt.Column) = + offsetToClangLineColumn(Input.Contents, *Offset); + + std::unique_ptr ContentsBuffer = + llvm::MemoryBuffer::getMemBufferCopy(Input.Contents, Input.FileName); + // The diagnostic options must be set before creating a CompilerInstance. + CI->getDiagnosticOpts().IgnoreWarnings = true; + // We reuse the preamble whether it's valid or not. This is a + // correctness/performance tradeoff: building without a preamble is slow, and + // completion is latency-sensitive. + // NOTE: we must call BeginSourceFile after prepareCompilerInstance. Otherwise + // the remapped buffers do not get freed. + auto Clang = prepareCompilerInstance( + std::move(CI), Input.Preamble, std::move(ContentsBuffer), + std::move(Input.PCHs), std::move(Input.VFS), DummyDiagsConsumer); + Clang->setCodeCompletionConsumer(Consumer.release()); + + SyntaxOnlyAction Action; + if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) { + log("BeginSourceFile() failed when running codeComplete for {0}", + Input.FileName); + return false; + } + if (Includes) + Clang->getPreprocessor().addPPCallbacks( + collectIncludeStructureCallback(Clang->getSourceManager(), Includes)); + if (!Action.Execute()) { + log("Execute() failed when running codeComplete for {0}", Input.FileName); + return false; + } + Action.EndSourceFile(); + + return true; +} + +// Should we allow index completions in the specified context? +bool allowIndex(CodeCompletionContext &CC) { + if (!contextAllowsIndex(CC.getKind())) + return false; + // We also avoid ClassName::bar (but allow namespace::bar). + auto Scope = CC.getCXXScopeSpecifier(); + if (!Scope) + return true; + NestedNameSpecifier *NameSpec = (*Scope)->getScopeRep(); + if (!NameSpec) + return true; + // We only query the index when qualifier is a namespace. + // If it's a class, we rely solely on sema completions. + switch (NameSpec->getKind()) { + case NestedNameSpecifier::Global: + case NestedNameSpecifier::Namespace: + case NestedNameSpecifier::NamespaceAlias: + return true; + case NestedNameSpecifier::Super: + case NestedNameSpecifier::TypeSpec: + case NestedNameSpecifier::TypeSpecWithTemplate: + // Unresolved inside a template. + case NestedNameSpecifier::Identifier: + return false; + } + llvm_unreachable("invalid NestedNameSpecifier kind"); +} + +} // namespace + +clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const { + clang::CodeCompleteOptions Result; + Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns; + Result.IncludeMacros = IncludeMacros; + Result.IncludeGlobals = true; + // We choose to include full comments and not do doxygen parsing in + // completion. + // FIXME: ideally, we should support doxygen in some form, e.g. do markdown + // formatting of the comments. + Result.IncludeBriefComments = false; + + // When an is used, Sema is responsible for completing the main file, + // the index can provide results from the preamble. + // Tell Sema not to deserialize the preamble to look for results. + Result.LoadExternal = !Index; + + return Result; +} + +// Runs Sema-based (AST) and Index-based completion, returns merged results. +// +// There are a few tricky considerations: +// - the AST provides information needed for the index query (e.g. which +// namespaces to search in). So Sema must start first. +// - we only want to return the top results (Opts.Limit). +// Building CompletionItems for everything else is wasteful, so we want to +// preserve the "native" format until we're done with scoring. +// - the data underlying Sema completion items is owned by the AST and various +// other arenas, which must stay alive for us to build CompletionItems. +// - we may get duplicate results from Sema and the Index, we need to merge. +// +// So we start Sema completion first, and do all our work in its callback. +// We use the Sema context information to query the index. +// Then we merge the two result sets, producing items that are Sema/Index/Both. +// These items are scored, and the top N are synthesized into the LSP response. +// Finally, we can clean up the data structures created by Sema completion. +// +// Main collaborators are: +// - semaCodeComplete sets up the compiler machinery to run code completion. +// - CompletionRecorder captures Sema completion results, including context. +// - SymbolIndex (Opts.Index) provides index completion results as Symbols +// - CompletionCandidates are the result of merging Sema and Index results. +// Each candidate points to an underlying CodeCompletionResult (Sema), a +// Symbol (Index), or both. It computes the result quality score. +// CompletionCandidate also does conversion to CompletionItem (at the end). +// - FuzzyMatcher scores how the candidate matches the partial identifier. +// This score is combined with the result quality score for the final score. +// - TopN determines the results with the best score. +class CodeCompleteFlow { + PathRef FileName; + IncludeStructure Includes; // Complete once the compiler runs. + const CodeCompleteOptions &Opts; + // Sema takes ownership of Recorder. Recorder is valid until Sema cleanup. + CompletionRecorder *Recorder = nullptr; + int NSema = 0, NIndex = 0, NBoth = 0; // Counters for logging. + bool Incomplete = false; // Would more be available with a higher limit? + llvm::Optional Filter; // Initialized once Sema runs. + std::vector QueryScopes; // Initialized once Sema runs. + // Include-insertion and proximity scoring rely on the include structure. + // This is available after Sema has run. + llvm::Optional Inserter; // Available during runWithSema. + llvm::Optional FileProximity; // Initialized once Sema runs. + +public: + // A CodeCompleteFlow object is only useful for calling run() exactly once. + CodeCompleteFlow(PathRef FileName, const IncludeStructure &Includes, + const CodeCompleteOptions &Opts) + : FileName(FileName), Includes(Includes), Opts(Opts) {} + + CodeCompleteResult run(const SemaCompleteInput &SemaCCInput) && { + trace::Span Tracer("CodeCompleteFlow"); + + // We run Sema code completion first. It builds an AST and calculates: + // - completion results based on the AST. + // - partial identifier and context. We need these for the index query. + CodeCompleteResult Output; + auto RecorderOwner = llvm::make_unique(Opts, [&]() { + assert(Recorder && "Recorder is not set"); + auto Style = + format::getStyle(format::DefaultFormatStyle, SemaCCInput.FileName, + format::DefaultFallbackStyle, SemaCCInput.Contents, + SemaCCInput.VFS.get()); + if (!Style) { + log("getStyle() failed for file {0}: {1}. Fallback is LLVM style.", + SemaCCInput.FileName, Style.takeError()); + Style = format::getLLVMStyle(); + } + // If preprocessor was run, inclusions from preprocessor callback should + // already be added to Includes. + Inserter.emplace( + SemaCCInput.FileName, SemaCCInput.Contents, *Style, + SemaCCInput.Command.Directory, + Recorder->CCSema->getPreprocessor().getHeaderSearchInfo()); + for (const auto &Inc : Includes.MainFileIncludes) + Inserter->addExisting(Inc); + + // Most of the cost of file proximity is in initializing the FileDistance + // structures based on the observed includes, once per query. Conceptually + // that happens here (though the per-URI-scheme initialization is lazy). + // The per-result proximity scoring is (amortized) very cheap. + FileDistanceOptions ProxOpts{}; // Use defaults. + const auto &SM = Recorder->CCSema->getSourceManager(); + llvm::StringMap ProxSources; + for (auto &Entry : Includes.includeDepth( + SM.getFileEntryForID(SM.getMainFileID())->getName())) { + auto &Source = ProxSources[Entry.getKey()]; + Source.Cost = Entry.getValue() * ProxOpts.IncludeCost; + // Symbols near our transitive includes are good, but only consider + // things in the same directory or below it. Otherwise there can be + // many false positives. + if (Entry.getValue() > 0) + Source.MaxUpTraversals = 1; + } + FileProximity.emplace(ProxSources, ProxOpts); + + Output = runWithSema(); + Inserter.reset(); // Make sure this doesn't out-live Clang. + SPAN_ATTACH(Tracer, "sema_completion_kind", + getCompletionKindString(Recorder->CCContext.getKind())); + log("Code complete: sema context {0}, query scopes [{1}]", + getCompletionKindString(Recorder->CCContext.getKind()), + llvm::join(QueryScopes.begin(), QueryScopes.end(), ",")); + }); + + Recorder = RecorderOwner.get(); + semaCodeComplete(std::move(RecorderOwner), Opts.getClangCompleteOpts(), + SemaCCInput, &Includes); + + SPAN_ATTACH(Tracer, "sema_results", NSema); + SPAN_ATTACH(Tracer, "index_results", NIndex); + SPAN_ATTACH(Tracer, "merged_results", NBoth); + SPAN_ATTACH(Tracer, "returned_results", int64_t(Output.Completions.size())); + SPAN_ATTACH(Tracer, "incomplete", Output.HasMore); + log("Code complete: {0} results from Sema, {1} from Index, " + "{2} matched, {3} returned{4}.", + NSema, NIndex, NBoth, Output.Completions.size(), + Output.HasMore ? " (incomplete)" : ""); + assert(!Opts.Limit || Output.Completions.size() <= Opts.Limit); + // We don't assert that isIncomplete means we hit a limit. + // Indexes may choose to impose their own limits even if we don't have one. + return Output; + } + +private: + // This is called by run() once Sema code completion is done, but before the + // Sema data structures are torn down. It does all the real work. + CodeCompleteResult runWithSema() { + Filter = FuzzyMatcher( + Recorder->CCSema->getPreprocessor().getCodeCompletionFilter()); + QueryScopes = getQueryScopes(Recorder->CCContext, + Recorder->CCSema->getSourceManager()); + // Sema provides the needed context to query the index. + // FIXME: in addition to querying for extra/overlapping symbols, we should + // explicitly request symbols corresponding to Sema results. + // We can use their signals even if the index can't suggest them. + // We must copy index results to preserve them, but there are at most Limit. + auto IndexResults = (Opts.Index && allowIndex(Recorder->CCContext)) + ? queryIndex() + : SymbolSlab(); + // Merge Sema and Index results, score them, and pick the winners. + auto Top = mergeResults(Recorder->Results, IndexResults); + // Convert the results to final form, assembling the expensive strings. + CodeCompleteResult Output; + for (auto &C : Top) { + Output.Completions.push_back(toCodeCompletion(C.first)); + Output.Completions.back().Score = C.second; + } + Output.HasMore = Incomplete; + Output.Context = Recorder->CCContext.getKind(); + return Output; + } + + SymbolSlab queryIndex() { + trace::Span Tracer("Query index"); + SPAN_ATTACH(Tracer, "limit", int64_t(Opts.Limit)); + + SymbolSlab::Builder ResultsBuilder; + // Build the query. + FuzzyFindRequest Req; + if (Opts.Limit) + Req.MaxCandidateCount = Opts.Limit; + Req.Query = Filter->pattern(); + Req.RestrictForCodeCompletion = true; + Req.Scopes = QueryScopes; + // FIXME: we should send multiple weighted paths here. + Req.ProximityPaths.push_back(FileName); + vlog("Code complete: fuzzyFind(\"{0}\", scopes=[{1}])", Req.Query, + llvm::join(Req.Scopes.begin(), Req.Scopes.end(), ",")); + // Run the query against the index. + if (Opts.Index->fuzzyFind( + Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); })) + Incomplete = true; + return std::move(ResultsBuilder).build(); + } + + // Merges Sema and Index results where possible, to form CompletionCandidates. + // Groups overloads if desired, to form CompletionCandidate::Bundles. + // The bundles are scored and top results are returned, best to worst. + std::vector + mergeResults(const std::vector &SemaResults, + const SymbolSlab &IndexResults) { + trace::Span Tracer("Merge and score results"); + std::vector Bundles; + llvm::DenseMap BundleLookup; + auto AddToBundles = [&](const CodeCompletionResult *SemaResult, + const Symbol *IndexResult) { + CompletionCandidate C; + C.SemaResult = SemaResult; + C.IndexResult = IndexResult; + C.Name = IndexResult ? IndexResult->Name : Recorder->getName(*SemaResult); + if (auto OverloadSet = Opts.BundleOverloads ? C.overloadSet() : 0) { + auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size()); + if (Ret.second) + Bundles.emplace_back(); + Bundles[Ret.first->second].push_back(std::move(C)); + } else { + Bundles.emplace_back(); + Bundles.back().push_back(std::move(C)); + } + }; + llvm::DenseSet UsedIndexResults; + auto CorrespondingIndexResult = + [&](const CodeCompletionResult &SemaResult) -> const Symbol * { + if (auto SymID = getSymbolID(SemaResult)) { + auto I = IndexResults.find(*SymID); + if (I != IndexResults.end()) { + UsedIndexResults.insert(&*I); + return &*I; + } + } + return nullptr; + }; + // Emit all Sema results, merging them with Index results if possible. + for (auto &SemaResult : Recorder->Results) + AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult)); + // Now emit any Index-only results. + for (const auto &IndexResult : IndexResults) { + if (UsedIndexResults.count(&IndexResult)) + continue; + AddToBundles(/*SemaResult=*/nullptr, &IndexResult); + } + // We only keep the best N results at any time, in "native" format. + TopN Top( + Opts.Limit == 0 ? std::numeric_limits::max() : Opts.Limit); + for (auto &Bundle : Bundles) + addCandidate(Top, std::move(Bundle)); + return std::move(Top).items(); + } + + Optional fuzzyScore(const CompletionCandidate &C) { + // Macros can be very spammy, so we only support prefix completion. + // We won't end up with underfull index results, as macros are sema-only. + if (C.SemaResult && C.SemaResult->Kind == CodeCompletionResult::RK_Macro && + !C.Name.startswith_lower(Filter->pattern())) + return None; + return Filter->match(C.Name); + } + + // Scores a candidate and adds it to the TopN structure. + void addCandidate(TopN &Candidates, + CompletionCandidate::Bundle Bundle) { + SymbolQualitySignals Quality; + SymbolRelevanceSignals Relevance; + Relevance.Context = Recorder->CCContext.getKind(); + Relevance.Query = SymbolRelevanceSignals::CodeComplete; + Relevance.FileProximityMatch = FileProximity.getPointer(); + auto &First = Bundle.front(); + if (auto FuzzyScore = fuzzyScore(First)) + Relevance.NameMatch = *FuzzyScore; + else + return; + SymbolOrigin Origin = SymbolOrigin::Unknown; + bool FromIndex = false; + for (const auto &Candidate : Bundle) { + if (Candidate.IndexResult) { + Quality.merge(*Candidate.IndexResult); + Relevance.merge(*Candidate.IndexResult); + Origin |= Candidate.IndexResult->Origin; + FromIndex = true; + } + if (Candidate.SemaResult) { + Quality.merge(*Candidate.SemaResult); + Relevance.merge(*Candidate.SemaResult); + Origin |= SymbolOrigin::AST; + } + } + + CodeCompletion::Scores Scores; + Scores.Quality = Quality.evaluate(); + Scores.Relevance = Relevance.evaluate(); + Scores.Total = evaluateSymbolAndRelevance(Scores.Quality, Scores.Relevance); + // NameMatch is in fact a multiplier on total score, so rescoring is sound. + Scores.ExcludingName = Relevance.NameMatch + ? Scores.Total / Relevance.NameMatch + : Scores.Quality; + + dlog("CodeComplete: {0} ({1}) = {2}\n{3}{4}\n", First.Name, + llvm::to_string(Origin), Scores.Total, llvm::to_string(Quality), + llvm::to_string(Relevance)); + + NSema += bool(Origin & SymbolOrigin::AST); + NIndex += FromIndex; + NBoth += bool(Origin & SymbolOrigin::AST) && FromIndex; + if (Candidates.push({std::move(Bundle), Scores})) + Incomplete = true; + } + + CodeCompletion toCodeCompletion(const CompletionCandidate::Bundle &Bundle) { + llvm::Optional Builder; + for (const auto &Item : Bundle) { + CodeCompletionString *SemaCCS = + Item.SemaResult ? Recorder->codeCompletionString(*Item.SemaResult) + : nullptr; + if (!Builder) + Builder.emplace(Recorder->CCSema->getASTContext(), Item, SemaCCS, + *Inserter, FileName, Opts); + else + Builder->add(Item, SemaCCS); + } + return Builder->build(); + } +}; + +CodeCompleteResult codeComplete(PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, + const IncludeStructure &PreambleInclusions, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, + CodeCompleteOptions Opts) { + return CodeCompleteFlow(FileName, PreambleInclusions, Opts) + .run({FileName, Command, Preamble, Contents, Pos, VFS, PCHs}); +} + +SignatureHelp signatureHelp(PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs) { + SignatureHelp Result; + clang::CodeCompleteOptions Options; + Options.IncludeGlobals = false; + Options.IncludeMacros = false; + Options.IncludeCodePatterns = false; + Options.IncludeBriefComments = false; + IncludeStructure PreambleInclusions; // Unused for signatureHelp + semaCodeComplete(llvm::make_unique(Options, Result), + Options, + {FileName, Command, Preamble, Contents, Pos, std::move(VFS), + std::move(PCHs)}); + return Result; +} + +bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) { + using namespace clang::ast_matchers; + auto InTopLevelScope = hasDeclContext( + anyOf(namespaceDecl(), translationUnitDecl(), linkageSpecDecl())); + return !match(decl(anyOf(InTopLevelScope, + hasDeclContext( + enumDecl(InTopLevelScope, unless(isScoped()))))), + ND, ASTCtx) + .empty(); +} + +CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const { + CompletionItem LSP; + LSP.label = (HeaderInsertion ? Opts.IncludeIndicator.Insert + : Opts.IncludeIndicator.NoInsert) + + (Opts.ShowOrigins ? "[" + llvm::to_string(Origin) + "]" : "") + + RequiredQualifier + Name + Signature; + + LSP.kind = Kind; + LSP.detail = BundleSize > 1 ? llvm::formatv("[{0} overloads]", BundleSize) + : ReturnType; + if (!Header.empty()) + LSP.detail += "\n" + Header; + LSP.documentation = Documentation; + LSP.sortText = sortText(Score.Total, Name); + LSP.filterText = Name; + LSP.insertText = RequiredQualifier + Name; + if (Opts.EnableSnippets) + LSP.insertText += SnippetSuffix; + LSP.insertTextFormat = Opts.EnableSnippets ? InsertTextFormat::Snippet + : InsertTextFormat::PlainText; + if (HeaderInsertion) + LSP.additionalTextEdits = {*HeaderInsertion}; + return LSP; +} + +raw_ostream &operator<<(raw_ostream &OS, const CodeCompletion &C) { + // For now just lean on CompletionItem. + return OS << C.render(CodeCompleteOptions()); +} + +raw_ostream &operator<<(raw_ostream &OS, const CodeCompleteResult &R) { + OS << "CodeCompleteResult: " << R.Completions.size() << (R.HasMore ? "+" : "") + << " (" << getCompletionKindString(R.Context) << ")" + << " items:\n"; + for (const auto &C : R.Completions) + OS << C << "\n"; + return OS; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/CodeComplete.h b/clangd/CodeComplete.h new file mode 100644 index 000000000..884149dfb --- /dev/null +++ b/clangd/CodeComplete.h @@ -0,0 +1,184 @@ +//===--- CodeComplete.h -----------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Code completion provides suggestions for what the user might type next. +// After "std::string S; S." we might suggest members of std::string. +// Signature help describes the parameters of a function as you type them. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETE_H + +#include "Headers.h" +#include "Logger.h" +#include "Path.h" +#include "Protocol.h" +#include "index/Index.h" +#include "clang/Frontend/PrecompiledPreamble.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "clang/Sema/CodeCompleteOptions.h" +#include "clang/Tooling/CompilationDatabase.h" + +namespace clang { +class NamedDecl; +class PCHContainerOperations; +namespace clangd { + +struct CodeCompleteOptions { + /// Returns options that can be passed to clang's completion engine. + clang::CodeCompleteOptions getClangCompleteOpts() const; + + /// When true, completion items will contain expandable code snippets in + /// completion (e.g. `return ${1:expression}` or `foo(${1:int a}, ${2:int + /// b})). + bool EnableSnippets = false; + + /// Add code patterns to completion results. + /// If EnableSnippets is false, this options is ignored and code patterns will + /// always be omitted. + bool IncludeCodePatterns = true; + + /// Add macros to code completion results. + bool IncludeMacros = true; + + /// Add comments to code completion results, if available. + bool IncludeComments = true; + + /// Include results that are not legal completions in the current context. + /// For example, private members are usually inaccessible. + bool IncludeIneligibleResults = false; + + /// Combine overloads into a single completion item where possible. + bool BundleOverloads = false; + + /// Limit the number of results returned (0 means no limit). + /// If more results are available, we set CompletionList.isIncomplete. + size_t Limit = 0; + + /// A visual indicator to prepend to the completion label to indicate whether + /// completion result would trigger an #include insertion or not. + struct IncludeInsertionIndicator { + std::string Insert = "•"; + std::string NoInsert = " "; + } IncludeIndicator; + + /// Expose origins of completion items in the label (for debugging). + bool ShowOrigins = false; + + // Populated internally by clangd, do not set. + /// If `Index` is set, it is used to augment the code completion + /// results. + /// FIXME(ioeric): we might want a better way to pass the index around inside + /// clangd. + const SymbolIndex *Index = nullptr; +}; + +// Semi-structured representation of a code-complete suggestion for our C++ API. +// We don't use the LSP structures here (unlike most features) as we want +// to expose more data to allow for more precise testing and evaluation. +struct CodeCompletion { + // The unqualified name of the symbol or other completion item. + std::string Name; + // The scope qualifier for the symbol name. e.g. "ns1::ns2::" + // Empty for non-symbol completions. Not inserted, but may be displayed. + std::string Scope; + // Text that must be inserted before the name, and displayed (e.g. base::). + std::string RequiredQualifier; + // Details to be displayed following the name. Not inserted. + std::string Signature; + // Text to be inserted following the name, in snippet format. + std::string SnippetSuffix; + // Type to be displayed for this completion. + std::string ReturnType; + std::string Documentation; + CompletionItemKind Kind = CompletionItemKind::Missing; + // This completion item may represent several symbols that can be inserted in + // the same way, such as function overloads. In this case BundleSize > 1, and + // the following fields are summaries: + // - Signature is e.g. "(...)" for functions. + // - SnippetSuffix is similarly e.g. "(${0})". + // - ReturnType may be empty + // - Documentation may be from one symbol, or a combination of several + // Other fields should apply equally to all bundled completions. + unsigned BundleSize = 1; + SymbolOrigin Origin = SymbolOrigin::Unknown; + // The header through which this symbol could be included. + // Quoted string as expected by an #include directive, e.g. "". + // Empty for non-symbol completions, or when not known. + std::string Header; + // Present if Header is set and should be inserted to use this item. + llvm::Optional HeaderInsertion; + + // Scores are used to rank completion items. + struct Scores { + // The score that items are ranked by. + float Total = 0.f; + + // The finalScore with the fuzzy name match score excluded. + // When filtering client-side, editors should calculate the new fuzzy score, + // whose scale is 0-1 (with 1 = prefix match, special case 2 = exact match), + // and recompute finalScore = fuzzyScore * symbolScore. + float ExcludingName = 0.f; + + // Component scores that contributed to the final score: + + // Quality describes how important we think this candidate is, + // independent of the query. + // e.g. symbols with lots of incoming references have higher quality. + float Quality = 0.f; + // Relevance describes how well this candidate matched the query. + // e.g. symbols from nearby files have higher relevance. + float Relevance = 0.f; + }; + Scores Score; + + // Serialize this to an LSP completion item. This is a lossy operation. + CompletionItem render(const CodeCompleteOptions &) const; +}; +raw_ostream &operator<<(raw_ostream &, const CodeCompletion &); +struct CodeCompleteResult { + std::vector Completions; + bool HasMore = false; + CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other; +}; +raw_ostream &operator<<(raw_ostream &, const CodeCompleteResult &); + +/// Get code completions at a specified \p Pos in \p FileName. +CodeCompleteResult codeComplete(PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, + const IncludeStructure &PreambleInclusions, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs, + CodeCompleteOptions Opts); + +/// Get signature help at a specified \p Pos in \p FileName. +SignatureHelp signatureHelp(PathRef FileName, + const tooling::CompileCommand &Command, + PrecompiledPreamble const *Preamble, + StringRef Contents, Position Pos, + IntrusiveRefCntPtr VFS, + std::shared_ptr PCHs); + +// For index-based completion, we only consider: +// * symbols in namespaces or translation unit scopes (e.g. no class +// members, no locals) +// * enum constants in unscoped enum decl (e.g. "red" in "enum {red};") +// * primary templates (no specializations) +// For the other cases, we let Clang do the completion because it does not +// need any non-local information and it will be much better at following +// lookup rules. Other symbols still appear in the index for other purposes, +// like workspace/symbols or textDocument/definition, but are not used for code +// completion. +bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx); +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/CodeCompletionStrings.cpp b/clangd/CodeCompletionStrings.cpp new file mode 100644 index 000000000..88b37daf8 --- /dev/null +++ b/clangd/CodeCompletionStrings.cpp @@ -0,0 +1,204 @@ +//===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "CodeCompletionStrings.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/RawCommentList.h" +#include "clang/Basic/SourceManager.h" +#include + +namespace clang { +namespace clangd { + +namespace { + +bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) { + return Chunk.Kind == CodeCompletionString::CK_Informative && + StringRef(Chunk.Text).endswith("::"); +} + +void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) { + for (const auto Character : Text) { + if (Character == '$' || Character == '}' || Character == '\\') + Out->push_back('\\'); + Out->push_back(Character); + } +} + +bool looksLikeDocComment(llvm::StringRef CommentText) { + // We don't report comments that only contain "special" chars. + // This avoids reporting various delimiters, like: + // ================= + // ----------------- + // ***************** + return CommentText.find_first_not_of("/*-= \t\r\n") != llvm::StringRef::npos; +} + +} // namespace + +std::string getDocComment(const ASTContext &Ctx, + const CodeCompletionResult &Result, + bool CommentsFromHeaders) { + // FIXME: clang's completion also returns documentation for RK_Pattern if they + // contain a pattern for ObjC properties. Unfortunately, there is no API to + // get this declaration, so we don't show documentation in that case. + if (Result.Kind != CodeCompletionResult::RK_Declaration) + return ""; + auto *Decl = Result.getDeclaration(); + if (!Decl || llvm::isa(Decl)) { + // Namespaces often have too many redecls for any particular redecl comment + // to be useful. Moreover, we often confuse file headers or generated + // comments with namespace comments. Therefore we choose to just ignore + // the comments for namespaces. + return ""; + } + const RawComment *RC = getCompletionComment(Ctx, Decl); + if (!RC) + return ""; + + // Sanity check that the comment does not come from the PCH. We choose to not + // write them into PCH, because they are racy and slow to load. + assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getLocStart())); + std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); + if (!looksLikeDocComment(Doc)) + return ""; + return Doc; +} + +std::string +getParameterDocComment(const ASTContext &Ctx, + const CodeCompleteConsumer::OverloadCandidate &Result, + unsigned ArgIndex, bool CommentsFromHeaders) { + auto *Func = Result.getFunction(); + if (!Func) + return ""; + const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex); + if (!RC) + return ""; + // Sanity check that the comment does not come from the PCH. We choose to not + // write them into PCH, because they are racy and slow to load. + assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getLocStart())); + std::string Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics()); + if (!looksLikeDocComment(Doc)) + return ""; + return Doc; +} + +void getSignature(const CodeCompletionString &CCS, std::string *Signature, + std::string *Snippet, std::string *RequiredQualifiers) { + unsigned ArgCount = 0; + for (const auto &Chunk : CCS) { + // Informative qualifier chunks only clutter completion results, skip + // them. + if (isInformativeQualifierChunk(Chunk)) + continue; + + switch (Chunk.Kind) { + case CodeCompletionString::CK_TypedText: + // The typed-text chunk is the actual name. We don't record this chunk. + // In general our string looks like . + // So once we see the name, any text we recorded so far should be + // reclassified as qualifiers. + if (RequiredQualifiers) + *RequiredQualifiers = std::move(*Signature); + Signature->clear(); + Snippet->clear(); + break; + case CodeCompletionString::CK_Text: + *Signature += Chunk.Text; + *Snippet += Chunk.Text; + break; + case CodeCompletionString::CK_Optional: + break; + case CodeCompletionString::CK_Placeholder: + *Signature += Chunk.Text; + ++ArgCount; + *Snippet += "${" + std::to_string(ArgCount) + ':'; + appendEscapeSnippet(Chunk.Text, Snippet); + *Snippet += '}'; + break; + case CodeCompletionString::CK_Informative: + // For example, the word "const" for a const method, or the name of + // the base class for methods that are part of the base class. + *Signature += Chunk.Text; + // Don't put the informative chunks in the snippet. + break; + case CodeCompletionString::CK_ResultType: + // This is not part of the signature. + break; + case CodeCompletionString::CK_CurrentParameter: + // This should never be present while collecting completion items, + // only while collecting overload candidates. + llvm_unreachable("Unexpected CK_CurrentParameter while collecting " + "CompletionItems"); + break; + case CodeCompletionString::CK_LeftParen: + case CodeCompletionString::CK_RightParen: + case CodeCompletionString::CK_LeftBracket: + case CodeCompletionString::CK_RightBracket: + case CodeCompletionString::CK_LeftBrace: + case CodeCompletionString::CK_RightBrace: + case CodeCompletionString::CK_LeftAngle: + case CodeCompletionString::CK_RightAngle: + case CodeCompletionString::CK_Comma: + case CodeCompletionString::CK_Colon: + case CodeCompletionString::CK_SemiColon: + case CodeCompletionString::CK_Equal: + case CodeCompletionString::CK_HorizontalSpace: + *Signature += Chunk.Text; + *Snippet += Chunk.Text; + break; + case CodeCompletionString::CK_VerticalSpace: + *Snippet += Chunk.Text; + // Don't even add a space to the signature. + break; + } + } +} + +std::string formatDocumentation(const CodeCompletionString &CCS, + llvm::StringRef DocComment) { + // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this + // information in the documentation field. + std::string Result; + const unsigned AnnotationCount = CCS.getAnnotationCount(); + if (AnnotationCount > 0) { + Result += "Annotation"; + if (AnnotationCount == 1) { + Result += ": "; + } else /* AnnotationCount > 1 */ { + Result += "s: "; + } + for (unsigned I = 0; I < AnnotationCount; ++I) { + Result += CCS.getAnnotation(I); + Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); + } + } + // Add brief documentation (if there is any). + if (!DocComment.empty()) { + if (!Result.empty()) { + // This means we previously added annotations. Add an extra newline + // character to make the annotations stand out. + Result.push_back('\n'); + } + Result += DocComment; + } + return Result; +} + +std::string getReturnType(const CodeCompletionString &CCS) { + for (const auto &Chunk : CCS) + if (Chunk.Kind == CodeCompletionString::CK_ResultType) + return Chunk.Text; + return ""; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/CodeCompletionStrings.h b/clangd/CodeCompletionStrings.h new file mode 100644 index 000000000..dac4ba566 --- /dev/null +++ b/clangd/CodeCompletionStrings.h @@ -0,0 +1,73 @@ +//===--- CodeCompletionStrings.h ---------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Functions for retrieving code completion information from +// `CodeCompletionString`. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CODECOMPLETIONSTRINGS_H + +#include "clang/Sema/CodeCompleteConsumer.h" + +namespace clang { +class ASTContext; + +namespace clangd { + +/// Gets a minimally formatted documentation comment of \p Result, with comment +/// markers stripped. See clang::RawComment::getFormattedText() for the detailed +/// explanation of how the comment text is transformed. +/// Returns empty string when no comment is available. +/// If \p CommentsFromHeaders parameter is set, only comments from the main +/// file will be returned. It is used to workaround crashes when parsing +/// comments in the stale headers, coming from completion preamble. +std::string getDocComment(const ASTContext &Ctx, + const CodeCompletionResult &Result, + bool CommentsFromHeaders); + +/// Gets a minimally formatted documentation for parameter of \p Result, +/// corresponding to argument number \p ArgIndex. +/// This currently looks for comments attached to the parameter itself, and +/// doesn't extract them from function documentation. +/// Returns empty string when no comment is available. +/// If \p CommentsFromHeaders parameter is set, only comments from the main +/// file will be returned. It is used to workaround crashes when parsing +/// comments in the stale headers, coming from completion preamble. +std::string +getParameterDocComment(const ASTContext &Ctx, + const CodeCompleteConsumer::OverloadCandidate &Result, + unsigned ArgIndex, bool CommentsFromHeaders); + +/// Formats the signature for an item, as a display string and snippet. +/// e.g. for const_reference std::vector::at(size_type) const, this returns: +/// *Signature = "(size_type) const" +/// *Snippet = "(${0:size_type})" +/// If set, RequiredQualifiers is the text that must be typed before the name. +/// e.g "Base::" when calling a base class member function that's hidden. +void getSignature(const CodeCompletionString &CCS, std::string *Signature, + std::string *Snippet, + std::string *RequiredQualifiers = nullptr); + +/// Assembles formatted documentation for a completion result. This includes +/// documentation comments and other relevant information like annotations. +/// +/// \param DocComment is a documentation comment for the original declaration, +/// it should be obtained via getDocComment or getParameterDocComment. +std::string formatDocumentation(const CodeCompletionString &CCS, + llvm::StringRef DocComment); + +/// Gets detail to be used as the detail field in an LSP completion item. This +/// is usually the return type of a function. +std::string getReturnType(const CodeCompletionString &CCS); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Compiler.cpp b/clangd/Compiler.cpp new file mode 100644 index 000000000..8fcc9a97c --- /dev/null +++ b/clangd/Compiler.cpp @@ -0,0 +1,84 @@ +//===--- Compiler.cpp -------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "Compiler.h" +#include "Logger.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" + +namespace clang { +namespace clangd { + +void IgnoreDiagnostics::log(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) { + SmallString<64> Message; + Info.FormatDiagnostic(Message); + + SmallString<64> Location; + if (Info.hasSourceManager() && Info.getLocation().isValid()) { + auto &SourceMgr = Info.getSourceManager(); + auto Loc = SourceMgr.getFileLoc(Info.getLocation()); + llvm::raw_svector_ostream OS(Location); + Loc.print(OS, SourceMgr); + OS << ":"; + } + + clangd::log("Ignored diagnostic. {0}{1}", Location, Message); +} + +void IgnoreDiagnostics::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) { + IgnoreDiagnostics::log(DiagLevel, Info); +} + +std::unique_ptr +prepareCompilerInstance(std::unique_ptr CI, + const PrecompiledPreamble *Preamble, + std::unique_ptr Buffer, + std::shared_ptr PCHs, + IntrusiveRefCntPtr VFS, + DiagnosticConsumer &DiagsClient) { + assert(VFS && "VFS is null"); + assert(!CI->getPreprocessorOpts().RetainRemappedFileBuffers && + "Setting RetainRemappedFileBuffers to true will cause a memory leak " + "of ContentsBuffer"); + + // NOTE: we use Buffer.get() when adding remapped files, so we have to make + // sure it will be released if no error is emitted. + if (Preamble) { + Preamble->OverridePreamble(*CI, VFS, Buffer.get()); + } else { + CI->getPreprocessorOpts().addRemappedFile( + CI->getFrontendOpts().Inputs[0].getFile(), Buffer.get()); + } + + auto Clang = llvm::make_unique(PCHs); + Clang->setInvocation(std::move(CI)); + Clang->createDiagnostics(&DiagsClient, false); + + if (auto VFSWithRemapping = createVFSFromCompilerInvocation( + Clang->getInvocation(), Clang->getDiagnostics(), VFS)) + VFS = VFSWithRemapping; + Clang->setVirtualFileSystem(VFS); + + Clang->setTarget(TargetInfo::CreateTargetInfo( + Clang->getDiagnostics(), Clang->getInvocation().TargetOpts)); + if (!Clang->hasTarget()) + return nullptr; + + // RemappedFileBuffers will handle the lifetime of the Buffer pointer, + // release it. + Buffer.release(); + return Clang; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Compiler.h b/clangd/Compiler.h new file mode 100644 index 000000000..ce058ad28 --- /dev/null +++ b/clangd/Compiler.h @@ -0,0 +1,53 @@ +//===--- Compiler.h ---------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Shared utilities for invoking the clang compiler. +// ClangdUnit takes care of much of this, but some features like CodeComplete +// run their own compile actions that share logic. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_COMPILER_H + +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/PrecompiledPreamble.h" + +namespace clang { +namespace clangd { + +class IgnoreDiagnostics : public DiagnosticConsumer { +public: + static void log(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info); + + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override; +}; + +/// Creates a compiler instance, configured so that: +/// - Contents of the parsed file are remapped to \p MainFile. +/// - Preamble is overriden to use PCH passed to this function. It means the +/// changes to the preamble headers or files included in the preamble are +/// not visible to this compiler instance. +/// - vfs::FileSystem is used for all underlying file accesses. The actual +/// vfs used by the compiler may be an overlay over the passed vfs. +/// Returns null on errors. When non-null value is returned, it is expected to +/// be consumed by FrontendAction::BeginSourceFile to properly destroy \p +/// MainFile. +std::unique_ptr prepareCompilerInstance( + std::unique_ptr, const PrecompiledPreamble *, + std::unique_ptr MainFile, + std::shared_ptr, + IntrusiveRefCntPtr, DiagnosticConsumer &); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Context.cpp b/clangd/Context.cpp new file mode 100644 index 000000000..1a9ef24ff --- /dev/null +++ b/clangd/Context.cpp @@ -0,0 +1,36 @@ +//===--- Context.cpp -----------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "Context.h" +#include + +namespace clang { +namespace clangd { + +Context Context::empty() { return Context(/*DataPtr=*/nullptr); } + +Context::Context(std::shared_ptr DataPtr) + : DataPtr(std::move(DataPtr)) {} + +Context Context::clone() const { return Context(DataPtr); } + +static Context ¤tContext() { + static thread_local auto C = Context::empty(); + return C; +} + +const Context &Context::current() { return currentContext(); } + +Context Context::swapCurrent(Context Replacement) { + std::swap(Replacement, currentContext()); + return Replacement; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Context.h b/clangd/Context.h new file mode 100644 index 000000000..7e14f86c1 --- /dev/null +++ b/clangd/Context.h @@ -0,0 +1,223 @@ +//===--- Context.h - Mechanism for passing implicit data --------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Context for storing and retrieving implicit data. Useful for passing implicit +// parameters on a per-request basis. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ + +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Compiler.h" +#include +#include + +namespace clang { +namespace clangd { + +/// Values in a Context are indexed by typed keys. +/// Key serves two purposes: +/// - it provides a lookup key for the context (each Key is unique), +/// - it makes lookup type-safe: a Key can only map to a T (or nothing). +/// +/// Example: +/// Key RequestID; +/// Key Version; +/// +/// Context Ctx = Context::empty().derive(RequestID, 10).derive(Version, 3); +/// assert(*Ctx.get(RequestID) == 10); +/// assert(*Ctx.get(Version) == 3); +/// +/// Keys are typically used across multiple functions, so most of the time you +/// would want to make them static class members or global variables. +template class Key { +public: + static_assert(!std::is_reference::value, + "Reference arguments to Key<> are not allowed"); + + constexpr Key() = default; + + Key(Key const &) = delete; + Key &operator=(Key const &) = delete; + Key(Key &&) = delete; + Key &operator=(Key &&) = delete; +}; + +/// A context is an immutable container for per-request data that must be +/// propagated through layers that don't care about it. An example is a request +/// ID that we may want to use when logging. +/// +/// Conceptually, a context is a heterogeneous map, T>. Each key has +/// an associated value type, which allows the map to be typesafe. +/// +/// There is an "ambient" context for each thread, Context::current(). +/// Most functions should read from this, and use WithContextValue or +/// WithContext to extend or replace the context within a block scope. +/// Only code dealing with threads and extension points should need to use +/// other Context objects. +/// +/// You can't add data to an existing context, instead you create a new +/// immutable context derived from it with extra data added. When you retrieve +/// data, the context will walk up the parent chain until the key is found. +class Context { +public: + /// Returns an empty root context that contains no data. + static Context empty(); + /// Returns the context for the current thread, creating it if needed. + static const Context ¤t(); + // Sets the current() context to Replacement, and returns the old context. + // Prefer to use WithContext or WithContextValue to do this safely. + static Context swapCurrent(Context Replacement); + +private: + struct Data; + Context(std::shared_ptr DataPtr); + +public: + /// Same as Context::empty(), please use Context::empty() instead. + /// Constructor is defined to workaround a bug in MSVC's version of STL. + /// (arguments of std::future<> must be default-construcitble in MSVC). + Context() = default; + + /// Copy operations for this class are deleted, use an explicit clone() method + /// when you need a copy of the context instead. + Context(Context const &) = delete; + Context &operator=(const Context &) = delete; + + Context(Context &&) = default; + Context &operator=(Context &&) = default; + + /// Get data stored for a typed \p Key. If values are not found + /// \returns Pointer to the data associated with \p Key. If no data is + /// specified for \p Key, return null. + template const Type *get(const Key &Key) const { + for (const Data *DataPtr = this->DataPtr.get(); DataPtr != nullptr; + DataPtr = DataPtr->Parent.get()) { + if (DataPtr->KeyPtr == &Key) + return static_cast(DataPtr->Value->getValuePtr()); + } + return nullptr; + } + + /// A helper to get a reference to a \p Key that must exist in the map. + /// Must not be called for keys that are not in the map. + template const Type &getExisting(const Key &Key) const { + auto Val = get(Key); + assert(Val && "Key does not exist"); + return *Val; + } + + /// Derives a child context + /// It is safe to move or destroy a parent context after calling derive(). + /// The child will keep its parent alive, and its data remains accessible. + template + Context derive(const Key &Key, + typename std::decay::type Value) const & { + return Context(std::make_shared(Data{ + /*Parent=*/DataPtr, &Key, + llvm::make_unique::type>>( + std::move(Value))})); + } + + template + Context + derive(const Key &Key, + typename std::decay::type Value) && /* takes ownership */ { + return Context(std::make_shared(Data{ + /*Parent=*/std::move(DataPtr), &Key, + llvm::make_unique::type>>( + std::move(Value))})); + } + + /// Derives a child context, using an anonymous key. + /// Intended for objects stored only for their destructor's side-effect. + template Context derive(Type &&Value) const & { + static Key::type> Private; + return derive(Private, std::forward(Value)); + } + + template Context derive(Type &&Value) && { + static Key::type> Private; + return std::move(*this).derive(Private, std::forward(Value)); + } + + /// Clone this context object. + Context clone() const; + +private: + class AnyStorage { + public: + virtual ~AnyStorage() = default; + virtual void *getValuePtr() = 0; + }; + + template class TypedAnyStorage : public Context::AnyStorage { + static_assert(std::is_same::type, T>::value, + "Argument to TypedAnyStorage must be decayed"); + + public: + TypedAnyStorage(T &&Value) : Value(std::move(Value)) {} + + void *getValuePtr() override { return &Value; } + + private: + T Value; + }; + + struct Data { + // We need to make sure Parent outlives the Value, so the order of members + // is important. We do that to allow classes stored in Context's child + // layers to store references to the data in the parent layers. + std::shared_ptr Parent; + const void *KeyPtr; + std::unique_ptr Value; + }; + + std::shared_ptr DataPtr; +}; + +/// WithContext replaces Context::current() with a provided scope. +/// When the WithContext is destroyed, the original scope is restored. +/// For extending the current context with new value, prefer WithContextValue. +class LLVM_NODISCARD WithContext { +public: + WithContext(Context C) : Restore(Context::swapCurrent(std::move(C))) {} + ~WithContext() { Context::swapCurrent(std::move(Restore)); } + WithContext(const WithContext &) = delete; + WithContext &operator=(const WithContext &) = delete; + WithContext(WithContext &&) = delete; + WithContext &operator=(WithContext &&) = delete; + +private: + Context Restore; +}; + +/// WithContextValue extends Context::current() with a single value. +/// When the WithContextValue is destroyed, the original scope is restored. +class LLVM_NODISCARD WithContextValue { +public: + template + WithContextValue(const Key &K, typename std::decay::type V) + : Restore(Context::current().derive(K, std::move(V))) {} + + // Anonymous values can be used for the destructor side-effect. + template + WithContextValue(T &&V) + : Restore(Context::current().derive(std::forward(V))) {} + +private: + WithContext Restore; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONTEXT_H_ diff --git a/clangd/Diagnostics.cpp b/clangd/Diagnostics.cpp new file mode 100644 index 000000000..73ac475ef --- /dev/null +++ b/clangd/Diagnostics.cpp @@ -0,0 +1,384 @@ +//===--- Diagnostics.cpp ----------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// + +#include "Diagnostics.h" +#include "Compiler.h" +#include "Logger.h" +#include "SourceCode.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/Capacity.h" +#include "llvm/Support/Path.h" +#include + +namespace clang { +namespace clangd { + +namespace { + +bool mentionsMainFile(const Diag &D) { + if (D.InsideMainFile) + return true; + // Fixes are always in the main file. + if (!D.Fixes.empty()) + return true; + for (auto &N : D.Notes) { + if (N.InsideMainFile) + return true; + } + return false; +} + +// Checks whether a location is within a half-open range. +// Note that clang also uses closed source ranges, which this can't handle! +bool locationInRange(SourceLocation L, CharSourceRange R, + const SourceManager &M) { + assert(R.isCharRange()); + if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) || + M.getFileID(R.getBegin()) != M.getFileID(L)) + return false; + return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); +} + +// Clang diags have a location (shown as ^) and 0 or more ranges (~~~~). +// LSP needs a single range. +Range diagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { + auto &M = D.getSourceManager(); + auto Loc = M.getFileLoc(D.getLocation()); + // Accept the first range that contains the location. + for (const auto &CR : D.getRanges()) { + auto R = Lexer::makeFileCharRange(CR, M, L); + if (locationInRange(Loc, R, M)) + return halfOpenToRange(M, R); + } + // The range may be given as a fixit hint instead. + for (const auto &F : D.getFixItHints()) { + auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L); + if (locationInRange(Loc, R, M)) + return halfOpenToRange(M, R); + } + // If no suitable range is found, just use the token at the location. + auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L); + if (!R.isValid()) // Fall back to location only, let the editor deal with it. + R = CharSourceRange::getCharRange(Loc); + return halfOpenToRange(M, R); +} + +TextEdit toTextEdit(const FixItHint &FixIt, const SourceManager &M, + const LangOptions &L) { + TextEdit Result; + Result.range = + halfOpenToRange(M, Lexer::makeFileCharRange(FixIt.RemoveRange, M, L)); + Result.newText = FixIt.CodeToInsert; + return Result; +} + +bool isInsideMainFile(const SourceLocation Loc, const SourceManager &M) { + return Loc.isValid() && M.isInMainFile(Loc); +} + +bool isInsideMainFile(const clang::Diagnostic &D) { + if (!D.hasSourceManager()) + return false; + + return isInsideMainFile(D.getLocation(), D.getSourceManager()); +} + +bool isNote(DiagnosticsEngine::Level L) { + return L == DiagnosticsEngine::Note || L == DiagnosticsEngine::Remark; +} + +llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { + switch (Lvl) { + case DiagnosticsEngine::Ignored: + return "ignored"; + case DiagnosticsEngine::Note: + return "note"; + case DiagnosticsEngine::Remark: + return "remark"; + case DiagnosticsEngine::Warning: + return "warning"; + case DiagnosticsEngine::Error: + return "error"; + case DiagnosticsEngine::Fatal: + return "fatal error"; + } + llvm_unreachable("unhandled DiagnosticsEngine::Level"); +} + +/// Prints a single diagnostic in a clang-like manner, the output includes +/// location, severity and error message. An example of the output message is: +/// +/// main.cpp:12:23: error: undeclared identifier +/// +/// For main file we only print the basename and for all other files we print +/// the filename on a separate line to provide a slightly more readable output +/// in the editors: +/// +/// dir1/dir2/dir3/../../dir4/header.h:12:23 +/// error: undeclared identifier +void printDiag(llvm::raw_string_ostream &OS, const DiagBase &D) { + if (D.InsideMainFile) { + // Paths to main files are often taken from compile_command.json, where they + // are typically absolute. To reduce noise we print only basename for them, + // it should not be confusing and saves space. + OS << llvm::sys::path::filename(D.File) << ":"; + } else { + OS << D.File << ":"; + } + // Note +1 to line and character. clangd::Range is zero-based, but when + // printing for users we want one-based indexes. + auto Pos = D.Range.start; + OS << (Pos.line + 1) << ":" << (Pos.character + 1) << ":"; + // The non-main-file paths are often too long, putting them on a separate + // line improves readability. + if (D.InsideMainFile) + OS << " "; + else + OS << "\n"; + OS << diagLeveltoString(D.Severity) << ": " << D.Message; +} + +/// Returns a message sent to LSP for the main diagnostic in \p D. +/// The message includes all the notes with their corresponding locations. +/// However, notes with fix-its are excluded as those usually only contain a +/// fix-it message and just add noise if included in the message for diagnostic. +/// Example output: +/// +/// no matching function for call to 'foo' +/// +/// main.cpp:3:5: note: candidate function not viable: requires 2 arguments +/// +/// dir1/dir2/dir3/../../dir4/header.h:12:23 +/// note: candidate function not viable: requires 3 arguments +std::string mainMessage(const Diag &D) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << D.Message; + for (auto &Note : D.Notes) { + OS << "\n\n"; + printDiag(OS, Note); + } + OS.flush(); + return Result; +} + +/// Returns a message sent to LSP for the note of the main diagnostic. +/// The message includes the main diagnostic to provide the necessary context +/// for the user to understand the note. +std::string noteMessage(const Diag &Main, const DiagBase &Note) { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << Note.Message; + OS << "\n\n"; + printDiag(OS, Main); + OS.flush(); + return Result; +} +} // namespace + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D) { + if (!D.InsideMainFile) + OS << "[in " << D.File << "] "; + return OS << D.Message; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F) { + OS << F.Message << " {"; + const char *Sep = ""; + for (const auto &Edit : F.Edits) { + OS << Sep << Edit; + Sep = ", "; + } + return OS << "}"; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D) { + OS << static_cast(D); + if (!D.Notes.empty()) { + OS << ", notes: {"; + const char *Sep = ""; + for (auto &Note : D.Notes) { + OS << Sep << Note; + Sep = ", "; + } + OS << "}"; + } + if (!D.Fixes.empty()) { + OS << ", fixes: {"; + const char *Sep = ""; + for (auto &Fix : D.Fixes) { + OS << Sep << Fix; + Sep = ", "; + } + } + return OS; +} + +void toLSPDiags( + const Diag &D, + llvm::function_ref)> OutFn) { + auto FillBasicFields = [](const DiagBase &D) -> clangd::Diagnostic { + clangd::Diagnostic Res; + Res.range = D.Range; + Res.severity = getSeverity(D.Severity); + return Res; + }; + + { + clangd::Diagnostic Main = FillBasicFields(D); + Main.message = mainMessage(D); + OutFn(std::move(Main), D.Fixes); + } + + for (auto &Note : D.Notes) { + if (!Note.InsideMainFile) + continue; + clangd::Diagnostic Res = FillBasicFields(Note); + Res.message = noteMessage(D, Note); + OutFn(std::move(Res), llvm::ArrayRef()); + } +} + +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!"); +} + +std::vector StoreDiags::take() { return std::move(Output); } + +void StoreDiags::BeginSourceFile(const LangOptions &Opts, + const Preprocessor *) { + LangOpts = Opts; +} + +void StoreDiags::EndSourceFile() { + flushLastDiag(); + LangOpts = llvm::None; +} + +void StoreDiags::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) { + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + if (!LangOpts || !Info.hasSourceManager()) { + IgnoreDiagnostics::log(DiagLevel, Info); + return; + } + + bool InsideMainFile = isInsideMainFile(Info); + + auto FillDiagBase = [&](DiagBase &D) { + D.Range = diagnosticRange(Info, *LangOpts); + llvm::SmallString<64> Message; + Info.FormatDiagnostic(Message); + D.Message = Message.str(); + D.InsideMainFile = InsideMainFile; + D.File = Info.getSourceManager().getFilename(Info.getLocation()); + D.Severity = DiagLevel; + return D; + }; + + auto AddFix = [&](bool SyntheticMessage) -> bool { + assert(!Info.getFixItHints().empty() && + "diagnostic does not have attached fix-its"); + if (!InsideMainFile) + return false; + + llvm::SmallVector Edits; + for (auto &FixIt : Info.getFixItHints()) { + if (!isInsideMainFile(FixIt.RemoveRange.getBegin(), + Info.getSourceManager())) + return false; + Edits.push_back(toTextEdit(FixIt, Info.getSourceManager(), *LangOpts)); + } + + llvm::SmallString<64> Message; + // If requested and possible, create a message like "change 'foo' to 'bar'". + if (SyntheticMessage && Info.getNumFixItHints() == 1) { + const auto &FixIt = Info.getFixItHint(0); + bool Invalid = false; + StringRef Remove = Lexer::getSourceText( + FixIt.RemoveRange, Info.getSourceManager(), *LangOpts, &Invalid); + StringRef Insert = FixIt.CodeToInsert; + if (!Invalid) { + llvm::raw_svector_ostream M(Message); + if (!Remove.empty() && !Insert.empty()) + M << "change '" << Remove << "' to '" << Insert << "'"; + else if (!Remove.empty()) + M << "remove '" << Remove << "'"; + else if (!Insert.empty()) + M << "insert '" << Insert << "'"; + // Don't allow source code to inject newlines into diagnostics. + std::replace(Message.begin(), Message.end(), '\n', ' '); + } + } + if (Message.empty()) // either !SytheticMessage, or we failed to make one. + Info.FormatDiagnostic(Message); + LastDiag->Fixes.push_back(Fix{Message.str(), std::move(Edits)}); + return true; + }; + + if (!isNote(DiagLevel)) { + // Handle the new main diagnostic. + flushLastDiag(); + + LastDiag = Diag(); + FillDiagBase(*LastDiag); + + if (!Info.getFixItHints().empty()) + AddFix(true /* try to invent a message instead of repeating the diag */); + } else { + // Handle a note to an existing diagnostic. + if (!LastDiag) { + assert(false && "Adding a note without main diagnostic"); + IgnoreDiagnostics::log(DiagLevel, Info); + return; + } + + if (!Info.getFixItHints().empty()) { + // A clang note with fix-it is not a separate diagnostic in clangd. We + // attach it as a Fix to the main diagnostic instead. + if (!AddFix(false /* use the note as the message */)) + IgnoreDiagnostics::log(DiagLevel, Info); + } else { + // A clang note without fix-its corresponds to clangd::Note. + Note N; + FillDiagBase(N); + + LastDiag->Notes.push_back(std::move(N)); + } + } +} + +void StoreDiags::flushLastDiag() { + if (!LastDiag) + return; + if (mentionsMainFile(*LastDiag)) + Output.push_back(std::move(*LastDiag)); + else + log("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File, + LastDiag->Message); + LastDiag.reset(); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Diagnostics.h b/clangd/Diagnostics.h new file mode 100644 index 000000000..b43f04ae5 --- /dev/null +++ b/clangd/Diagnostics.h @@ -0,0 +1,98 @@ +//===--- Diagnostics.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_DIAGNOSTICS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIAGNOSTICS_H + +#include "Path.h" +#include "Protocol.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringSet.h" +#include +#include + +namespace clang { +namespace clangd { + +/// Contains basic information about a diagnostic. +struct DiagBase { + std::string Message; + // Intended to be used only in error messages. + // May be relative, absolute or even artifically constructed. + std::string File; + clangd::Range Range; + DiagnosticsEngine::Level Severity = DiagnosticsEngine::Note; + // Since File is only descriptive, we store a separate flag to distinguish + // diags from the main file. + bool InsideMainFile = false; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const DiagBase &D); + +/// Represents a single fix-it that editor can apply to fix the error. +struct Fix { + /// Message for the fix-it. + std::string Message; + /// TextEdits from clang's fix-its. Must be non-empty. + llvm::SmallVector Edits; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Fix &F); + +/// Represents a note for the diagnostic. Severity of notes can only be 'note' +/// or 'remark'. +struct Note : DiagBase {}; + +/// A top-level diagnostic that may have Notes and Fixes. +struct Diag : DiagBase { + /// Elaborate on the problem, usually pointing to a related piece of code. + std::vector Notes; + /// *Alternative* fixes for this diagnostic, one should be chosen. + std::vector Fixes; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diag &D); + +/// Conversion to LSP diagnostics. Formats the error message of each diagnostic +/// to include all its notes. Notes inside main file are also provided as +/// separate diagnostics with their corresponding fixits. Notes outside main +/// file do not have a corresponding LSP diagnostic, but can still be included +/// as part of their main diagnostic's message. +void toLSPDiags( + const Diag &D, + llvm::function_ref)> OutFn); + +/// Convert from clang diagnostic level to LSP severity. +int getSeverity(DiagnosticsEngine::Level L); + +/// StoreDiags collects the diagnostics that can later be reported by +/// clangd. It groups all notes for a diagnostic into a single Diag +/// and filters out diagnostics that don't mention the main file (i.e. neither +/// the diag itself nor its notes are in the main file). +class StoreDiags : public DiagnosticConsumer { +public: + std::vector take(); + + void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override; + void EndSourceFile() override; + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override; + +private: + void flushLastDiag(); + + std::vector Output; + llvm::Optional LangOpts; + llvm::Optional LastDiag; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/DraftStore.cpp b/clangd/DraftStore.cpp new file mode 100644 index 000000000..8e1cf8891 --- /dev/null +++ b/clangd/DraftStore.cpp @@ -0,0 +1,107 @@ +//===--- 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" +#include "SourceCode.h" +#include "llvm/Support/Errc.h" + +using namespace clang; +using namespace clang::clangd; + +llvm::Optional DraftStore::getDraft(PathRef File) const { + std::lock_guard Lock(Mutex); + + auto It = Drafts.find(File); + if (It == Drafts.end()) + return llvm::None; + + return It->second; +} + +std::vector DraftStore::getActiveFiles() const { + std::lock_guard Lock(Mutex); + std::vector ResultVector; + + for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++) + ResultVector.push_back(DraftIt->getKey()); + + return ResultVector; +} + +void DraftStore::addDraft(PathRef File, StringRef Contents) { + std::lock_guard Lock(Mutex); + + Drafts[File] = Contents; +} + +llvm::Expected DraftStore::updateDraft( + PathRef File, llvm::ArrayRef Changes) { + std::lock_guard Lock(Mutex); + + auto EntryIt = Drafts.find(File); + if (EntryIt == Drafts.end()) { + return llvm::make_error( + "Trying to do incremental update on non-added document: " + File, + llvm::errc::invalid_argument); + } + + std::string Contents = EntryIt->second; + + for (const TextDocumentContentChangeEvent &Change : Changes) { + if (!Change.range) { + Contents = Change.text; + continue; + } + + const Position &Start = Change.range->start; + llvm::Expected StartIndex = + positionToOffset(Contents, Start, false); + if (!StartIndex) + return StartIndex.takeError(); + + const Position &End = Change.range->end; + llvm::Expected EndIndex = positionToOffset(Contents, End, false); + if (!EndIndex) + return EndIndex.takeError(); + + if (*EndIndex < *StartIndex) + return llvm::make_error( + llvm::formatv( + "Range's end position ({0}) is before start position ({1})", End, + Start), + llvm::errc::invalid_argument); + + if (Change.rangeLength && + (ssize_t)(*EndIndex - *StartIndex) != *Change.rangeLength) + return llvm::make_error( + llvm::formatv("Change's rangeLength ({0}) doesn't match the " + "computed range length ({1}).", + *Change.rangeLength, *EndIndex - *StartIndex), + llvm::errc::invalid_argument); + + std::string NewContents; + NewContents.reserve(*StartIndex + Change.text.length() + + (Contents.length() - *EndIndex)); + + NewContents = Contents.substr(0, *StartIndex); + NewContents += Change.text; + NewContents += Contents.substr(*EndIndex); + + Contents = std::move(NewContents); + } + + EntryIt->second = Contents; + return Contents; +} + +void DraftStore::removeDraft(PathRef File) { + std::lock_guard Lock(Mutex); + + Drafts.erase(File); +} diff --git a/clangd/DraftStore.h b/clangd/DraftStore.h new file mode 100644 index 000000000..90a2d2cfc --- /dev/null +++ b/clangd/DraftStore.h @@ -0,0 +1,60 @@ +//===--- 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 "Protocol.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringMap.h" +#include +#include +#include + +namespace clang { +namespace clangd { + +/// A thread-safe container for files opened in a workspace, addressed by +/// filenames. The contents are owned by the DraftStore. This class supports +/// both whole and incremental updates of the documents. +class DraftStore { +public: + /// \return Contents of the stored document. + /// For untracked files, a llvm::None is returned. + llvm::Optional getDraft(PathRef File) const; + + /// \return List of names of the drafts in this store. + std::vector getActiveFiles() const; + + /// Replace contents of the draft for \p File with \p Contents. + void addDraft(PathRef File, StringRef Contents); + + /// Update the contents of the draft for \p File based on \p Changes. + /// If a position in \p Changes is invalid (e.g. out-of-range), the + /// draft is not modified. + /// + /// \return The new version of the draft for \p File, or an error if the + /// changes couldn't be applied. + llvm::Expected + updateDraft(PathRef File, + llvm::ArrayRef Changes); + + /// Remove the draft from the store. + void removeDraft(PathRef File); + +private: + mutable std::mutex Mutex; + llvm::StringMap Drafts; +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/FSProvider.h b/clangd/FSProvider.h new file mode 100644 index 000000000..70dfb441a --- /dev/null +++ b/clangd/FSProvider.h @@ -0,0 +1,42 @@ +//===--- FSProvider.h - VFS provider for ClangdServer ------------*- 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_FSPROVIDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FSPROVIDER_H + +#include "clang/Basic/VirtualFileSystem.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" + +namespace clang { +namespace clangd { + +// Wrapper for vfs::FileSystem for use in multithreaded programs like clangd. +// As FileSystem is not threadsafe, concurrent threads must each obtain one. +class FileSystemProvider { +public: + virtual ~FileSystemProvider() = default; + /// Called by ClangdServer to obtain a vfs::FileSystem to be used for parsing. + /// Context::current() will be the context passed to the clang entrypoint, + /// such as addDocument(), and will also be propagated to result callbacks. + /// Embedders may use this to isolate filesystem accesses. + virtual IntrusiveRefCntPtr getFileSystem() = 0; +}; + +class RealFileSystemProvider : public FileSystemProvider { +public: + // FIXME: returns the single real FS instance, which is not threadsafe. + IntrusiveRefCntPtr getFileSystem() override { + return vfs::getRealFileSystem(); + } +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/FileDistance.cpp b/clangd/FileDistance.cpp new file mode 100644 index 000000000..a0ce25b48 --- /dev/null +++ b/clangd/FileDistance.cpp @@ -0,0 +1,171 @@ +//===--- FileDistance.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. +// +//===----------------------------------------------------------------------===// +// +// The FileDistance structure allows calculating the minimum distance to paths +// in a single tree. +// We simply walk up the path's ancestors until we find a node whose cost is +// known, and add the cost of walking back down. Initialization ensures this +// gives the correct path to the roots. +// We cache the results, so that the runtime is O(|A|), where A is the set of +// all distinct ancestors of visited paths. +// +// Example after initialization with /=2, /bar=0, DownCost = 1: +// / = 2 +// /bar = 0 +// +// After querying /foo/bar and /bar/foo: +// / = 2 +// /bar = 0 +// /bar/foo = 1 +// /foo = 3 +// /foo/bar = 4 +// +// URIDistance creates FileDistance lazily for each URI scheme encountered. In +// practice this is a small constant factor. +// +//===-------------------------------------------------------------------------// + +#include "FileDistance.h" +#include "Logger.h" +#include "llvm/ADT/STLExtras.h" +#include + +namespace clang { +namespace clangd { +using namespace llvm; + +// Convert a path into the canonical form. +// Canonical form is either "/", or "/segment" * N: +// C:\foo\bar --> /c:/foo/bar +// /foo/ --> /foo +// a/b/c --> /a/b/c +static SmallString<128> canonicalize(StringRef Path) { + SmallString<128> Result = Path.rtrim('/'); + native(Result, sys::path::Style::posix); + if (Result.empty() || Result.front() != '/') + Result.insert(Result.begin(), '/'); + return Result; +} + +constexpr const unsigned FileDistance::kUnreachable; + +FileDistance::FileDistance(StringMap Sources, + const FileDistanceOptions &Opts) + : Opts(Opts) { + llvm::DenseMap> DownEdges; + // Compute the best distance following only up edges. + // Keep track of down edges, in case we can use them to improve on this. + for (const auto &S : Sources) { + auto Canonical = canonicalize(S.getKey()); + dlog("Source {0} = {1}, MaxUp = {2}", Canonical, S.second.Cost, + S.second.MaxUpTraversals); + // Walk up to ancestors of this source, assigning cost. + StringRef Rest = Canonical; + llvm::hash_code Hash = hash_value(Rest); + for (unsigned I = 0; !Rest.empty(); ++I) { + Rest = parent_path(Rest, sys::path::Style::posix); + auto NextHash = hash_value(Rest); + auto &Down = DownEdges[NextHash]; + if (std::find(Down.begin(), Down.end(), Hash) == Down.end()) + DownEdges[NextHash].push_back(Hash); + // We can't just break after MaxUpTraversals, must still set DownEdges. + if (I > S.getValue().MaxUpTraversals) { + if (Cache.find(Hash) != Cache.end()) + break; + } else { + unsigned Cost = S.getValue().Cost + I * Opts.UpCost; + auto R = Cache.try_emplace(Hash, Cost); + if (!R.second) { + if (Cost < R.first->second) { + R.first->second = Cost; + } else { + // If we're not the best way to get to this path, stop assigning. + break; + } + } + } + Hash = NextHash; + } + } + // Now propagate scores parent -> child if that's an improvement. + // BFS ensures we propagate down chains (must visit parents before children). + std::queue Next; + for (auto Child : DownEdges.lookup(hash_value(llvm::StringRef("")))) + Next.push(Child); + while (!Next.empty()) { + auto ParentCost = Cache.lookup(Next.front()); + for (auto Child : DownEdges.lookup(Next.front())) { + auto &ChildCost = + Cache.try_emplace(Child, kUnreachable).first->getSecond(); + if (ParentCost + Opts.DownCost < ChildCost) + ChildCost = ParentCost + Opts.DownCost; + Next.push(Child); + } + Next.pop(); + } +} + +unsigned FileDistance::distance(StringRef Path) { + auto Canonical = canonicalize(Path); + unsigned Cost = kUnreachable; + SmallVector Ancestors; + // Walk up ancestors until we find a path we know the distance for. + for (StringRef Rest = Canonical; !Rest.empty(); + Rest = parent_path(Rest, sys::path::Style::posix)) { + auto Hash = hash_value(Rest); + auto It = Cache.find(Hash); + if (It != Cache.end()) { + Cost = It->second; + break; + } + Ancestors.push_back(Hash); + } + // Now we know the costs for (known node, queried node]. + // Fill these in, walking down the directory tree. + for (hash_code Hash : reverse(Ancestors)) { + if (Cost != kUnreachable) + Cost += Opts.DownCost; + Cache.try_emplace(Hash, Cost); + } + dlog("distance({0} = {1})", Path, Cost); + return Cost; +} + +unsigned URIDistance::distance(llvm::StringRef URI) { + auto R = Cache.try_emplace(llvm::hash_value(URI), FileDistance::kUnreachable); + if (!R.second) + return R.first->getSecond(); + if (auto U = clangd::URI::parse(URI)) { + dlog("distance({0} = {1})", URI, U->body()); + R.first->second = forScheme(U->scheme()).distance(U->body()); + } else { + log("URIDistance::distance() of unparseable {0}: {1}", URI, U.takeError()); + } + return R.first->second; +} + +FileDistance &URIDistance::forScheme(llvm::StringRef Scheme) { + auto &Delegate = ByScheme[Scheme]; + if (!Delegate) { + llvm::StringMap SchemeSources; + for (const auto &Source : Sources) { + if (auto U = clangd::URI::create(Source.getKey(), Scheme)) + SchemeSources.try_emplace(U->body(), Source.getValue()); + else + consumeError(U.takeError()); + } + dlog("FileDistance for scheme {0}: {1}/{2} sources", Scheme, + SchemeSources.size(), Sources.size()); + Delegate.reset(new FileDistance(std::move(SchemeSources), Opts)); + } + return *Delegate; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/FileDistance.h b/clangd/FileDistance.h new file mode 100644 index 000000000..f07804fcb --- /dev/null +++ b/clangd/FileDistance.h @@ -0,0 +1,109 @@ +//===--- FileDistance.h - File proximity scoring -----------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This library measures the distance between file paths. +// It's used for ranking symbols, e.g. in code completion. +// |foo/bar.h -> foo/bar.h| = 0. +// |foo/bar.h -> foo/baz.h| < |foo/bar.h -> baz.h|. +// This is an edit-distance, where edits go up or down the directory tree. +// It's not symmetrical, the costs of going up and down may not match. +// +// Dealing with multiple sources: +// In practice we care about the distance from a source file, but files near +// its main-header and #included files are considered "close". +// So we start with a set of (anchor, cost) pairs, and call the distance to a +// path the minimum of `cost + |source -> path|`. +// +// We allow each source to limit the number of up-traversals paths may start +// with. Up-traversals may reach things that are not "semantically near". +// +// Symbol URI schemes: +// Symbol locations may be represented by URIs rather than file paths directly. +// In this case we want to perform distance computations in URI space rather +// than in file-space, without performing redundant conversions. +// Therefore we have a lookup structure that accepts URIs, so that intermediate +// calculations for the same scheme can be reused. +// +// Caveats: +// Assuming up and down traversals each have uniform costs is simplistic. +// Often there are "semantic roots" whose children are almost unrelated. +// (e.g. /usr/include/, or / in an umbrella repository). We ignore this. +// +//===----------------------------------------------------------------------===// + +#include "URI.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/StringSaver.h" + +namespace clang { +namespace clangd { + +struct FileDistanceOptions { + unsigned UpCost = 2; // |foo/bar.h -> foo| + unsigned DownCost = 1; // |foo -> foo/bar.h| + unsigned IncludeCost = 2; // |foo.cc -> included_header.h| +}; + +struct SourceParams { + // Base cost for paths starting at this source. + unsigned Cost = 0; + // Limits the number of upwards traversals allowed from this source. + unsigned MaxUpTraversals = std::numeric_limits::max(); +}; + +// Supports lookups to find the minimum distance to a file from any source. +// This object should be reused, it memoizes intermediate computations. +class FileDistance { +public: + static constexpr unsigned kUnreachable = std::numeric_limits::max(); + + FileDistance(llvm::StringMap Sources, + const FileDistanceOptions &Opts = {}); + + // Computes the minimum distance from any source to the file path. + unsigned distance(llvm::StringRef Path); + +private: + // Costs computed so far. Always contains sources and their ancestors. + // We store hash codes only. Collisions are rare and consequences aren't dire. + llvm::DenseMap Cache; + FileDistanceOptions Opts; +}; + +// Supports lookups like FileDistance, but the lookup keys are URIs. +// We convert each of the sources to the scheme of the URI and do a FileDistance +// comparison on the bodies. +class URIDistance { +public: + URIDistance(llvm::StringMap Sources, + const FileDistanceOptions &Opts = {}) + : Sources(Sources), Opts(Opts) {} + + // Computes the minimum distance from any source to the URI. + // Only sources that can be mapped into the URI's scheme are considered. + unsigned distance(llvm::StringRef URI); + +private: + // Returns the FileDistance for a URI scheme, creating it if needed. + FileDistance &forScheme(llvm::StringRef Scheme); + + // We cache the results using the original strings so we can skip URI parsing. + llvm::DenseMap Cache; + llvm::StringMap Sources; + llvm::StringMap> ByScheme; + FileDistanceOptions Opts; +}; + +} // namespace clangd +} // namespace clang diff --git a/clangd/FindSymbols.cpp b/clangd/FindSymbols.cpp new file mode 100644 index 000000000..c1ee67897 --- /dev/null +++ b/clangd/FindSymbols.cpp @@ -0,0 +1,279 @@ +//===--- FindSymbols.cpp ------------------------------------*- C++-*------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "FindSymbols.h" + +#include "AST.h" +#include "ClangdUnit.h" +#include "FuzzyMatch.h" +#include "Logger.h" +#include "Quality.h" +#include "SourceCode.h" +#include "index/Index.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Index/IndexingAction.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" + +#define DEBUG_TYPE "FindSymbols" + +namespace clang { +namespace clangd { + +namespace { + +// Convert a index::SymbolKind to clangd::SymbolKind (LSP) +// Note, some are not perfect matches and should be improved when this LSP +// issue is addressed: +// https://github.com/Microsoft/language-server-protocol/issues/344 +SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) { + switch (Kind) { + case index::SymbolKind::Unknown: + return SymbolKind::Variable; + case index::SymbolKind::Module: + return SymbolKind::Module; + case index::SymbolKind::Namespace: + return SymbolKind::Namespace; + case index::SymbolKind::NamespaceAlias: + return SymbolKind::Namespace; + case index::SymbolKind::Macro: + return SymbolKind::String; + case index::SymbolKind::Enum: + return SymbolKind::Enum; + case index::SymbolKind::Struct: + return SymbolKind::Struct; + case index::SymbolKind::Class: + return SymbolKind::Class; + case index::SymbolKind::Protocol: + return SymbolKind::Interface; + case index::SymbolKind::Extension: + return SymbolKind::Interface; + case index::SymbolKind::Union: + return SymbolKind::Class; + case index::SymbolKind::TypeAlias: + return SymbolKind::Class; + case index::SymbolKind::Function: + return SymbolKind::Function; + case index::SymbolKind::Variable: + return SymbolKind::Variable; + case index::SymbolKind::Field: + return SymbolKind::Field; + case index::SymbolKind::EnumConstant: + return SymbolKind::EnumMember; + case index::SymbolKind::InstanceMethod: + case index::SymbolKind::ClassMethod: + case index::SymbolKind::StaticMethod: + return SymbolKind::Method; + case index::SymbolKind::InstanceProperty: + case index::SymbolKind::ClassProperty: + case index::SymbolKind::StaticProperty: + return SymbolKind::Property; + case index::SymbolKind::Constructor: + case index::SymbolKind::Destructor: + return SymbolKind::Method; + case index::SymbolKind::ConversionFunction: + return SymbolKind::Function; + case index::SymbolKind::Parameter: + return SymbolKind::Variable; + case index::SymbolKind::Using: + return SymbolKind::Namespace; + } + llvm_unreachable("invalid symbol kind"); +} + +using ScoredSymbolInfo = std::pair; +struct ScoredSymbolGreater { + bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) { + if (L.first != R.first) + return L.first > R.first; + return L.second.name < R.second.name; // Earlier name is better. + } +}; + +} // namespace + +llvm::Expected> +getWorkspaceSymbols(StringRef Query, int Limit, const SymbolIndex *const Index, + StringRef HintPath) { + std::vector Result; + if (Query.empty() || !Index) + return Result; + + auto Names = splitQualifiedName(Query); + + FuzzyFindRequest Req; + Req.Query = Names.second; + + // FuzzyFind doesn't want leading :: qualifier + bool IsGlobalQuery = Names.first.consume_front("::"); + // Restrict results to the scope in the query string if present (global or + // not). + if (IsGlobalQuery || !Names.first.empty()) + Req.Scopes = {Names.first}; + if (Limit) + Req.MaxCandidateCount = Limit; + TopN Top(Req.MaxCandidateCount); + FuzzyMatcher Filter(Req.Query); + Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) { + // Prefer the definition over e.g. a function declaration in a header + auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; + auto Uri = URI::parse(CD.FileURI); + if (!Uri) { + log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", + CD.FileURI, Sym.Name); + return; + } + auto Path = URI::resolve(*Uri, HintPath); + if (!Path) { + log("Workspace symbol: Could not resolve path for URI '{0}' for symbol " + "'{1}'.", + Uri->toString(), Sym.Name); + return; + } + Location L; + L.uri = URIForFile((*Path)); + Position Start, End; + Start.line = CD.Start.Line; + Start.character = CD.Start.Column; + End.line = CD.End.Line; + End.character = CD.End.Column; + L.range = {Start, End}; + SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind); + std::string Scope = Sym.Scope; + StringRef ScopeRef = Scope; + ScopeRef.consume_back("::"); + SymbolInformation Info = {Sym.Name, SK, L, ScopeRef}; + + SymbolQualitySignals Quality; + Quality.merge(Sym); + SymbolRelevanceSignals Relevance; + Relevance.Query = SymbolRelevanceSignals::Generic; + if (auto NameMatch = Filter.match(Sym.Name)) + Relevance.NameMatch = *NameMatch; + else { + log("Workspace symbol: {0} didn't match query {1}", Sym.Name, + Filter.pattern()); + return; + } + Relevance.merge(Sym); + auto Score = + evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate()); + dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score, + Quality, Relevance); + + Top.push({Score, std::move(Info)}); + }); + for (auto &R : std::move(Top).items()) + Result.push_back(std::move(R.second)); + return Result; +} + +namespace { +/// Finds document symbols in the main file of the AST. +class DocumentSymbolsConsumer : public index::IndexDataConsumer { + ASTContext &AST; + std::vector Symbols; + // We are always list document for the same file, so cache the value. + llvm::Optional MainFileUri; + +public: + DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {} + std::vector takeSymbols() { return std::move(Symbols); } + + void initialize(ASTContext &Ctx) override { + // Compute the absolute path of the main file which we will use for all + // results. + const SourceManager &SM = AST.getSourceManager(); + const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID()); + if (!F) + return; + auto FilePath = getAbsoluteFilePath(F, SM); + if (FilePath) + MainFileUri = URIForFile(*FilePath); + } + + bool shouldIncludeSymbol(const NamedDecl *ND) { + if (!ND || ND->isImplicit()) + return false; + // Skip anonymous declarations, e.g (anonymous enum/class/struct). + if (ND->getDeclName().isEmpty()) + return false; + return true; + } + + bool + handleDeclOccurence(const Decl *, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + assert(ASTNode.OrigD); + // No point in continuing the index consumer if we could not get the + // absolute path of the main file. + if (!MainFileUri) + return false; + // We only want declarations and definitions, i.e. no references. + if (!(Roles & static_cast(index::SymbolRole::Declaration) || + Roles & static_cast(index::SymbolRole::Definition))) + return true; + SourceLocation NameLoc = findNameLoc(ASTNode.OrigD); + const SourceManager &SourceMgr = AST.getSourceManager(); + // We should be only be looking at "local" decls in the main file. + if (!SourceMgr.isWrittenInMainFile(NameLoc)) { + // Even thought we are visiting only local (non-preamble) decls, + // we can get here when in the presense of "extern" decls. + return true; + } + const NamedDecl *ND = llvm::dyn_cast(ASTNode.OrigD); + if (!shouldIncludeSymbol(ND)) + return true; + + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(NameLoc, 0, SourceMgr, AST.getLangOpts()); + Position Begin = sourceLocToPosition(SourceMgr, NameLoc); + Position End = sourceLocToPosition(SourceMgr, EndLoc); + Range R = {Begin, End}; + Location L; + L.uri = *MainFileUri; + L.range = R; + + std::string QName = printQualifiedName(*ND); + StringRef Scope, Name; + std::tie(Scope, Name) = splitQualifiedName(QName); + Scope.consume_back("::"); + + index::SymbolInfo SymInfo = index::getSymbolInfo(ND); + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + + SymbolInformation SI; + SI.name = Name; + SI.kind = SK; + SI.location = L; + SI.containerName = Scope; + Symbols.push_back(std::move(SI)); + return true; + } +}; +} // namespace + +llvm::Expected> +getDocumentSymbols(ParsedAST &AST) { + DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext()); + + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; + IndexOpts.IndexFunctionLocals = false; + indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(), + DocumentSymbolsCons, IndexOpts); + + return DocumentSymbolsCons.takeSymbols(); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/FindSymbols.h b/clangd/FindSymbols.h new file mode 100644 index 000000000..4e6e75a91 --- /dev/null +++ b/clangd/FindSymbols.h @@ -0,0 +1,45 @@ +//===--- FindSymbols.h --------------------------------------*- C++-*------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Queries that provide a list of symbols matching a string. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H + +#include "Protocol.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace clangd { +class ParsedAST; +class SymbolIndex; + +/// Searches for the symbols matching \p Query. The syntax of \p Query can be +/// the non-qualified name or fully qualified of a symbol. For example, "vector" +/// will match the symbol std::vector and "std::vector" would also match it. +/// Direct children of scopes (namepaces, etc) can be listed with a trailing +/// "::". For example, "std::" will list all children of the std namespace and +/// "::" alone will list all children of the global namespace. +/// \p Limit limits the number of results returned (0 means no limit). +/// \p HintPath This is used when resolving URIs. If empty, URI resolution can +/// fail if a hint path is required for the scheme of a specific URI. +llvm::Expected> +getWorkspaceSymbols(llvm::StringRef Query, int Limit, + const SymbolIndex *const Index, llvm::StringRef HintPath); + +/// Retrieves the symbols contained in the "main file" section of an AST in the +/// same order that they appear. +llvm::Expected> +getDocumentSymbols(ParsedAST &AST); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Function.h b/clangd/Function.h new file mode 100644 index 000000000..88a5bc000 --- /dev/null +++ b/clangd/Function.h @@ -0,0 +1,89 @@ +//===--- Function.h - Utility callable wrappers -----------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file provides utilities for callable objects. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUNCTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUNCTION_H + +#include "llvm/ADT/FunctionExtras.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace clang { +namespace clangd { + +/// A Callback is a void function that accepts Expected. +/// This is accepted by ClangdServer functions that logically return T. +template +using Callback = llvm::unique_function)>; + +/// Stores a callable object (Func) and arguments (Args) and allows to call the +/// callable with provided arguments later using `operator ()`. The arguments +/// are std::forward'ed into the callable in the body of `operator()`. Therefore +/// `operator()` can only be called once, as some of the arguments could be +/// std::move'ed into the callable on first call. +template struct ForwardBinder { + using Tuple = std::tuple::type, + typename std::decay::type...>; + Tuple FuncWithArguments; +#ifndef NDEBUG + bool WasCalled = false; +#endif + +public: + ForwardBinder(Tuple FuncWithArguments) + : FuncWithArguments(std::move(FuncWithArguments)) {} + +private: + template + auto CallImpl(llvm::integer_sequence Seq, + RestArgs &&... Rest) + -> decltype(std::get<0>(this->FuncWithArguments)( + std::forward(std::get(this->FuncWithArguments))..., + std::forward(Rest)...)) { + return std::get<0>(this->FuncWithArguments)( + std::forward(std::get(this->FuncWithArguments))..., + std::forward(Rest)...); + } + +public: + template + auto operator()(RestArgs &&... Rest) + -> decltype(this->CallImpl(llvm::index_sequence_for(), + std::forward(Rest)...)) { + +#ifndef NDEBUG + assert(!WasCalled && "Can only call result of Bind once."); + WasCalled = true; +#endif + return CallImpl(llvm::index_sequence_for(), + std::forward(Rest)...); + } +}; + +/// Creates an object that stores a callable (\p F) and first arguments to the +/// callable (\p As) and allows to call \p F with \Args at a later point. +/// Similar to std::bind, but also works with move-only \p F and \p As. +/// +/// The returned object must be called no more than once, as \p As are +/// std::forwarded'ed (therefore can be moved) into \p F during the call. +template +ForwardBinder Bind(Func F, Args &&... As) { + return ForwardBinder( + std::make_tuple(std::forward(F), std::forward(As)...)); +} + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/FuzzyMatch.cpp b/clangd/FuzzyMatch.cpp new file mode 100644 index 000000000..b81020afb --- /dev/null +++ b/clangd/FuzzyMatch.cpp @@ -0,0 +1,395 @@ +//===--- FuzzyMatch.h - Approximate identifier matching ---------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// To check for a match between a Pattern ('u_p') and a Word ('unique_ptr'), +// we consider the possible partial match states: +// +// u n i q u e _ p t r +// +--------------------- +// |A . . . . . . . . . . +// u| +// |. . . . . . . . . . . +// _| +// |. . . . . . . O . . . +// p| +// |. . . . . . . . . . B +// +// Each dot represents some prefix of the pattern being matched against some +// prefix of the word. +// - A is the initial state: '' matched against '' +// - O is an intermediate state: 'u_' matched against 'unique_' +// - B is the target state: 'u_p' matched against 'unique_ptr' +// +// We aim to find the best path from A->B. +// - Moving right (consuming a word character) +// Always legal: not all word characters must match. +// - Moving diagonally (consuming both a word and pattern character) +// Legal if the characters match. +// - Moving down (consuming a pattern character) is never legal. +// Never legal: all pattern characters must match something. +// Characters are matched case-insensitively. +// The first pattern character may only match the start of a word segment. +// +// The scoring is based on heuristics: +// - when matching a character, apply a bonus or penalty depending on the +// match quality (does case match, do word segments align, etc) +// - when skipping a character, apply a penalty if it hurts the match +// (it starts a word segment, or splits the matched region, etc) +// +// These heuristics require the ability to "look backward" one character, to +// see whether it was matched or not. Therefore the dynamic-programming matrix +// has an extra dimension (last character matched). +// Each entry also has an additional flag indicating whether the last-but-one +// character matched, which is needed to trace back through the scoring table +// and reconstruct the match. +// +// We treat strings as byte-sequences, so only ASCII has first-class support. +// +// This algorithm was inspired by VS code's client-side filtering, and aims +// to be mostly-compatible. +// +//===----------------------------------------------------------------------===// + +#include "FuzzyMatch.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/Format.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +constexpr int FuzzyMatcher::MaxPat; +constexpr int FuzzyMatcher::MaxWord; + +static char lower(char C) { return C >= 'A' && C <= 'Z' ? C + ('a' - 'A') : C; } +// A "negative infinity" score that won't overflow. +// We use this to mark unreachable states and forbidden solutions. +// Score field is 15 bits wide, min value is -2^14, we use half of that. +static constexpr int AwfulScore = -(1 << 13); +static bool isAwful(int S) { return S < AwfulScore / 2; } +static constexpr int PerfectBonus = 3; // Perfect per-pattern-char score. + +FuzzyMatcher::FuzzyMatcher(StringRef Pattern) + : PatN(std::min(MaxPat, Pattern.size())), + ScoreScale(PatN ? float{1} / (PerfectBonus * PatN) : 0), WordN(0) { + std::copy(Pattern.begin(), Pattern.begin() + PatN, Pat); + for (int I = 0; I < PatN; ++I) + LowPat[I] = lower(Pat[I]); + Scores[0][0][Miss] = {0, Miss}; + Scores[0][0][Match] = {AwfulScore, Miss}; + for (int P = 0; P <= PatN; ++P) + for (int W = 0; W < P; ++W) + for (Action A : {Miss, Match}) + Scores[P][W][A] = {AwfulScore, Miss}; + PatTypeSet = + calculateRoles(StringRef(Pat, PatN), makeMutableArrayRef(PatRole, PatN)); +} + +Optional FuzzyMatcher::match(StringRef Word) { + if (!(WordContainsPattern = init(Word))) + return None; + if (!PatN) + return 1; + buildGraph(); + auto Best = std::max(Scores[PatN][WordN][Miss].Score, + Scores[PatN][WordN][Match].Score); + if (isAwful(Best)) + return None; + float Score = + ScoreScale * std::min(PerfectBonus * PatN, std::max(0, Best)); + // If the pattern is as long as the word, we have an exact string match, + // since every pattern character must match something. + if (WordN == PatN) + Score *= 2; // May not be perfect 2 if case differs in a significant way. + return Score; +} + +// We get CharTypes from a lookup table. Each is 2 bits, 4 fit in each byte. +// The top 6 bits of the char select the byte, the bottom 2 select the offset. +// e.g. 'q' = 010100 01 = byte 28 (55), bits 3-2 (01) -> Lower. +constexpr static uint8_t CharTypes[] = { + 0x00, 0x00, 0x00, 0x00, // Control characters + 0x00, 0x00, 0x00, 0x00, // Control characters + 0xff, 0xff, 0xff, 0xff, // Punctuation + 0x55, 0x55, 0xf5, 0xff, // Numbers->Lower, more Punctuation. + 0xab, 0xaa, 0xaa, 0xaa, // @ and A-O + 0xaa, 0xaa, 0xea, 0xff, // P-Z, more Punctuation. + 0x57, 0x55, 0x55, 0x55, // ` and a-o + 0x55, 0x55, 0xd5, 0x3f, // p-z, Punctuation, DEL. + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // Bytes over 127 -> Lower. + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // (probably UTF-8). + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, +}; + +// The Role can be determined from the Type of a character and its neighbors: +// +// Example | Chars | Type | Role +// ---------+--------------+----- +// F(o)oBar | Foo | Ull | Tail +// Foo(B)ar | oBa | lUl | Head +// (f)oo | ^fo | Ell | Head +// H(T)TP | HTT | UUU | Tail +// +// Our lookup table maps a 6 bit key (Prev, Curr, Next) to a 2-bit Role. +// A byte packs 4 Roles. (Prev, Curr) selects a byte, Next selects the offset. +// e.g. Lower, Upper, Lower -> 01 10 01 -> byte 6 (aa), bits 3-2 (10) -> Head. +constexpr static uint8_t CharRoles[] = { + // clang-format off + // Curr= Empty Lower Upper Separ + /* Prev=Empty */ 0x00, 0xaa, 0xaa, 0xff, // At start, Lower|Upper->Head + /* Prev=Lower */ 0x00, 0x55, 0xaa, 0xff, // In word, Upper->Head;Lower->Tail + /* Prev=Upper */ 0x00, 0x55, 0x59, 0xff, // Ditto, but U(U)U->Tail + /* Prev=Separ */ 0x00, 0xaa, 0xaa, 0xff, // After separator, like at start + // clang-format on +}; + +template static T packedLookup(const uint8_t *Data, int I) { + return static_cast((Data[I >> 2] >> ((I & 3) * 2)) & 3); +} +CharTypeSet calculateRoles(StringRef Text, MutableArrayRef Roles) { + assert(Text.size() == Roles.size()); + if (Text.size() == 0) + return 0; + CharType Type = packedLookup(CharTypes, Text[0]); + CharTypeSet TypeSet = 1 << Type; + // Types holds a sliding window of (Prev, Curr, Next) types. + // Initial value is (Empty, Empty, type of Text[0]). + int Types = Type; + // Rotate slides in the type of the next character. + auto Rotate = [&](CharType T) { Types = ((Types << 2) | T) & 0x3f; }; + for (unsigned I = 0; I < Text.size() - 1; ++I) { + // For each character, rotate in the next, and look up the role. + Type = packedLookup(CharTypes, Text[I + 1]); + TypeSet |= 1 << Type; + Rotate(Type); + Roles[I] = packedLookup(CharRoles, Types); + } + // For the last character, the "next character" is Empty. + Rotate(Empty); + Roles[Text.size() - 1] = packedLookup(CharRoles, Types); + return TypeSet; +} + +// Sets up the data structures matching Word. +// Returns false if we can cheaply determine that no match is possible. +bool FuzzyMatcher::init(StringRef NewWord) { + WordN = std::min(MaxWord, NewWord.size()); + if (PatN > WordN) + return false; + std::copy(NewWord.begin(), NewWord.begin() + WordN, Word); + if (PatN == 0) + return true; + for (int I = 0; I < WordN; ++I) + LowWord[I] = lower(Word[I]); + + // Cheap subsequence check. + for (int W = 0, P = 0; P != PatN; ++W) { + if (W == WordN) + return false; + if (LowWord[W] == LowPat[P]) + ++P; + } + + // FIXME: some words are hard to tokenize algorithmically. + // e.g. vsprintf is V S Print F, and should match [pri] but not [int]. + // We could add a tokenization dictionary for common stdlib names. + WordTypeSet = calculateRoles(StringRef(Word, WordN), + makeMutableArrayRef(WordRole, WordN)); + return true; +} + +// The forwards pass finds the mappings of Pattern onto Word. +// Score = best score achieved matching Word[..W] against Pat[..P]. +// Unlike other tables, indices range from 0 to N *inclusive* +// Matched = whether we chose to match Word[W] with Pat[P] or not. +// +// Points are mostly assigned to matched characters, with 1 being a good score +// and 3 being a great one. So we treat the score range as [0, 3 * PatN]. +// This range is not strict: we can apply larger bonuses/penalties, or penalize +// non-matched characters. +void FuzzyMatcher::buildGraph() { + for (int W = 0; W < WordN; ++W) { + Scores[0][W + 1][Miss] = {Scores[0][W][Miss].Score - skipPenalty(W, Miss), + Miss}; + Scores[0][W + 1][Match] = {AwfulScore, Miss}; + } + for (int P = 0; P < PatN; ++P) { + for (int W = P; W < WordN; ++W) { + auto &Score = Scores[P + 1][W + 1], &PreMiss = Scores[P + 1][W]; + + auto MatchMissScore = PreMiss[Match].Score; + auto MissMissScore = PreMiss[Miss].Score; + if (P < PatN - 1) { // Skipping trailing characters is always free. + MatchMissScore -= skipPenalty(W, Match); + MissMissScore -= skipPenalty(W, Miss); + } + Score[Miss] = (MatchMissScore > MissMissScore) + ? ScoreInfo{MatchMissScore, Match} + : ScoreInfo{MissMissScore, Miss}; + + auto &PreMatch = Scores[P][W]; + auto MatchMatchScore = + allowMatch(P, W, Match) + ? PreMatch[Match].Score + matchBonus(P, W, Match) + : AwfulScore; + auto MissMatchScore = allowMatch(P, W, Miss) + ? PreMatch[Miss].Score + matchBonus(P, W, Miss) + : AwfulScore; + Score[Match] = (MatchMatchScore > MissMatchScore) + ? ScoreInfo{MatchMatchScore, Match} + : ScoreInfo{MissMatchScore, Miss}; + } + } +} + +bool FuzzyMatcher::allowMatch(int P, int W, Action Last) const { + if (LowPat[P] != LowWord[W]) + return false; + // We require a "strong" match: + // - for the first pattern character. [foo] !~ "barefoot" + // - after a gap. [pat] !~ "patnther" + if (Last == Miss) { + // We're banning matches outright, so conservatively accept some other cases + // where our segmentation might be wrong: + // - allow matching B in ABCDef (but not in NDEBUG) + // - we'd like to accept print in sprintf, but too many false positives + if (WordRole[W] == Tail && + (Word[W] == LowWord[W] || !(WordTypeSet & 1 << Lower))) + return false; + } + return true; +} + +int FuzzyMatcher::skipPenalty(int W, Action Last) const { + int S = 0; + if (WordRole[W] == Head) // Skipping a segment. + S += 1; + if (Last == Match) // Non-consecutive match. + S += 2; // We'd rather skip a segment than split our match. + return S; +} + +int FuzzyMatcher::matchBonus(int P, int W, Action Last) const { + assert(LowPat[P] == LowWord[W]); + int S = 1; + // Bonus: pattern so far is a (case-insensitive) prefix of the word. + if (P == W) // We can't skip pattern characters, so we must have matched all. + ++S; + // Bonus: case matches, or a Head in the pattern aligns with one in the word. + if ((Pat[P] == Word[W] && ((PatTypeSet & 1 << Upper) || P == W)) || + (PatRole[P] == Head && WordRole[W] == Head)) + ++S; + // Penalty: matching inside a segment (and previous char wasn't matched). + if (WordRole[W] == Tail && P && Last == Miss) + S -= 3; + // Penalty: a Head in the pattern matches in the middle of a word segment. + if (PatRole[P] == Head && WordRole[W] == Tail) + --S; + // Penalty: matching the first pattern character in the middle of a segment. + if (P == 0 && WordRole[W] == Tail) + S -= 4; + assert(S <= PerfectBonus); + return S; +} + +llvm::SmallString<256> FuzzyMatcher::dumpLast(llvm::raw_ostream &OS) const { + llvm::SmallString<256> Result; + OS << "=== Match \"" << StringRef(Word, WordN) << "\" against [" + << StringRef(Pat, PatN) << "] ===\n"; + if (PatN == 0) { + OS << "Pattern is empty: perfect match.\n"; + return Result = StringRef(Word, WordN); + } + if (WordN == 0) { + OS << "Word is empty: no match.\n"; + return Result; + } + if (!WordContainsPattern) { + OS << "Substring check failed.\n"; + return Result; + } else if (isAwful(std::max(Scores[PatN][WordN][Match].Score, + Scores[PatN][WordN][Miss].Score))) { + OS << "Substring check passed, but all matches are forbidden\n"; + } + if (!(PatTypeSet & 1 << Upper)) + OS << "Lowercase query, so scoring ignores case\n"; + + // Traverse Matched table backwards to reconstruct the Pattern/Word mapping. + // The Score table has cumulative scores, subtracting along this path gives + // us the per-letter scores. + Action Last = + (Scores[PatN][WordN][Match].Score > Scores[PatN][WordN][Miss].Score) + ? Match + : Miss; + int S[MaxWord]; + Action A[MaxWord]; + for (int W = WordN - 1, P = PatN - 1; W >= 0; --W) { + A[W] = Last; + const auto &Cell = Scores[P + 1][W + 1][Last]; + if (Last == Match) + --P; + const auto &Prev = Scores[P + 1][W][Cell.Prev]; + S[W] = Cell.Score - Prev.Score; + Last = Cell.Prev; + } + for (int I = 0; I < WordN; ++I) { + if (A[I] == Match && (I == 0 || A[I - 1] == Miss)) + Result.push_back('['); + if (A[I] == Miss && I > 0 && A[I - 1] == Match) + Result.push_back(']'); + Result.push_back(Word[I]); + } + if (A[WordN - 1] == Match) + Result.push_back(']'); + + for (char C : StringRef(Word, WordN)) + OS << " " << C << " "; + OS << "\n"; + for (int I = 0, J = 0; I < WordN; I++) + OS << " " << (A[I] == Match ? Pat[J++] : ' ') << " "; + OS << "\n"; + for (int I = 0; I < WordN; I++) + OS << format("%2d ", S[I]); + OS << "\n"; + + OS << "\nSegmentation:"; + OS << "\n'" << StringRef(Word, WordN) << "'\n "; + for (int I = 0; I < WordN; ++I) + OS << "?-+ "[static_cast(WordRole[I])]; + OS << "\n[" << StringRef(Pat, PatN) << "]\n "; + for (int I = 0; I < PatN; ++I) + OS << "?-+ "[static_cast(PatRole[I])]; + OS << "\n"; + + OS << "\nScoring table (last-Miss, last-Match):\n"; + OS << " | "; + for (char C : StringRef(Word, WordN)) + OS << " " << C << " "; + OS << "\n"; + OS << "-+----" << std::string(WordN * 4, '-') << "\n"; + for (int I = 0; I <= PatN; ++I) { + for (Action A : {Miss, Match}) { + OS << ((I && A == Miss) ? Pat[I - 1] : ' ') << "|"; + for (int J = 0; J <= WordN; ++J) { + if (!isAwful(Scores[I][J][A].Score)) + OS << format("%3d%c", Scores[I][J][A].Score, + Scores[I][J][A].Prev == Match ? '*' : ' '); + else + OS << " "; + } + OS << "\n"; + } + } + + return Result; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/FuzzyMatch.h b/clangd/FuzzyMatch.h new file mode 100644 index 000000000..f0c7e722a --- /dev/null +++ b/clangd/FuzzyMatch.h @@ -0,0 +1,137 @@ +//===--- FuzzyMatch.h - Approximate identifier matching ---------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements fuzzy-matching of strings against identifiers. +// It indicates both the existence and quality of a match: +// 'eb' matches both 'emplace_back' and 'embed', the former has a better score. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUZZYMATCH_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FUZZYMATCH_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { + +// Utilities for word segmentation. +// FuzzyMatcher already incorporates this logic, so most users don't need this. +// +// A name like "fooBar_baz" consists of several parts foo, bar, baz. +// Aligning segmentation of word and pattern improves the fuzzy-match. +// For example: [lol] matches "LaughingOutLoud" better than "LionPopulation" +// +// First we classify each character into types (uppercase, lowercase, etc). +// Then we look at the sequence: e.g. [upper, lower] is the start of a segment. + +// We distinguish the types of characters that affect segmentation. +// It's not obvious how to segment digits, we treat them as lowercase letters. +// As we don't decode UTF-8, we treat bytes over 127 as lowercase too. +// This means we require exact (case-sensitive) match for those characters. +enum CharType : unsigned char { + Empty = 0, // Before-the-start and after-the-end (and control chars). + Lower = 1, // Lowercase letters, digits, and non-ASCII bytes. + Upper = 2, // Uppercase letters. + Punctuation = 3, // ASCII punctuation (including Space) +}; +// A CharTypeSet is a bitfield representing all the character types in a word. +// Its bits are 1< Roles); + +// A matcher capable of matching and scoring strings against a single pattern. +// It's optimized for matching against many strings - match() does not allocate. +class FuzzyMatcher { +public: + // Characters beyond MaxPat are ignored. + FuzzyMatcher(llvm::StringRef Pattern); + + // If Word matches the pattern, return a score indicating the quality match. + // Scores usually fall in a [0,1] range, with 1 being a very good score. + // "Super" scores in (1,2] are possible if the pattern is the full word. + // Characters beyond MaxWord are ignored. + llvm::Optional match(llvm::StringRef Word); + + llvm::StringRef pattern() const { return llvm::StringRef(Pat, PatN); } + bool empty() const { return PatN == 0; } + + // Dump internal state from the last match() to the stream, for debugging. + // Returns the pattern with [] around matched characters, e.g. + // [u_p] + "unique_ptr" --> "[u]nique[_p]tr" + llvm::SmallString<256> dumpLast(llvm::raw_ostream &) const; + +private: + // We truncate the pattern and the word to bound the cost of matching. + constexpr static int MaxPat = 63, MaxWord = 127; + // Action describes how a word character was matched to the pattern. + // It should be an enum, but this causes bitfield problems: + // - for MSVC the enum type must be explicitly unsigned for correctness + // - GCC 4.8 complains not all values fit if the type is unsigned + using Action = bool; + constexpr static Action Miss = false; // Word character was skipped. + constexpr static Action Match = true; // Matched against a pattern character. + + bool init(llvm::StringRef Word); + void buildGraph(); + bool allowMatch(int P, int W, Action Last) const; + int skipPenalty(int W, Action Last) const; + int matchBonus(int P, int W, Action Last) const; + + // Pattern data is initialized by the constructor, then constant. + char Pat[MaxPat]; // Pattern data + int PatN; // Length + char LowPat[MaxPat]; // Pattern in lowercase + CharRole PatRole[MaxPat]; // Pattern segmentation info + CharTypeSet PatTypeSet; // Bitmask of 1< Argv = {"clang"}; + // Clang treats .h files as C by default, resulting in unhelpful diagnostics. + // Parsing as Objective C++ is friendly to more cases. + if (llvm::sys::path::extension(File) == ".h") + Argv.push_back("-xobjective-c++-header"); + Argv.push_back(File); + return tooling::CompileCommand(llvm::sys::path::parent_path(File), + llvm::sys::path::filename(File), + std::move(Argv), + /*Output=*/""); +} + +DirectoryBasedGlobalCompilationDatabase:: + DirectoryBasedGlobalCompilationDatabase( + llvm::Optional CompileCommandsDir) + : CompileCommandsDir(std::move(CompileCommandsDir)) {} + +DirectoryBasedGlobalCompilationDatabase:: + ~DirectoryBasedGlobalCompilationDatabase() = default; + +llvm::Optional +DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const { + if (auto CDB = getCDBForFile(File)) { + auto Candidates = CDB->getCompileCommands(File); + if (!Candidates.empty()) { + addExtraFlags(File, Candidates.front()); + return std::move(Candidates.front()); + } + } else { + log("Failed to find compilation database for {0}", File); + } + return llvm::None; +} + +tooling::CompileCommand +DirectoryBasedGlobalCompilationDatabase::getFallbackCommand( + PathRef File) const { + auto C = GlobalCompilationDatabase::getFallbackCommand(File); + addExtraFlags(File, C); + return C; +} + +void DirectoryBasedGlobalCompilationDatabase::setCompileCommandsDir(Path P) { + std::lock_guard Lock(Mutex); + CompileCommandsDir = P; + CompilationDatabases.clear(); +} + +void DirectoryBasedGlobalCompilationDatabase::setExtraFlagsForFile( + PathRef File, std::vector ExtraFlags) { + std::lock_guard Lock(Mutex); + ExtraFlagsForFile[File] = std::move(ExtraFlags); +} + +void DirectoryBasedGlobalCompilationDatabase::addExtraFlags( + PathRef File, tooling::CompileCommand &C) const { + std::lock_guard Lock(Mutex); + + auto It = ExtraFlagsForFile.find(File); + if (It == ExtraFlagsForFile.end()) + return; + + auto &Args = C.CommandLine; + assert(Args.size() >= 2 && "Expected at least [compiler, source file]"); + // The last argument of CommandLine is the name of the input file. + // Add ExtraFlags before it. + Args.insert(Args.end() - 1, It->second.begin(), It->second.end()); +} + +tooling::CompilationDatabase * +DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { + // FIXME(ibiryukov): Invalidate cached compilation databases on changes + auto CachedIt = CompilationDatabases.find(Dir); + if (CachedIt != CompilationDatabases.end()) + return CachedIt->second.get(); + std::string Error = ""; + auto CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); + if (CDB) + CDB = tooling::inferMissingCompileCommands(std::move(CDB)); + auto Result = CDB.get(); + CompilationDatabases.insert(std::make_pair(Dir, std::move(CDB))); + return Result; +} + +tooling::CompilationDatabase * +DirectoryBasedGlobalCompilationDatabase::getCDBForFile(PathRef File) const { + namespace path = llvm::sys::path; + assert((path::is_absolute(File, path::Style::posix) || + path::is_absolute(File, path::Style::windows)) && + "path must be absolute"); + + std::lock_guard Lock(Mutex); + if (CompileCommandsDir) + return getCDBInDirLocked(*CompileCommandsDir); + for (auto Path = path::parent_path(File); !Path.empty(); + Path = path::parent_path(Path)) + if (auto CDB = getCDBInDirLocked(Path)) + return CDB; + return nullptr; +} + +CachingCompilationDb::CachingCompilationDb( + const GlobalCompilationDatabase &InnerCDB) + : InnerCDB(InnerCDB) {} + +llvm::Optional +CachingCompilationDb::getCompileCommand(PathRef File) const { + std::unique_lock Lock(Mut); + auto It = Cached.find(File); + if (It != Cached.end()) + return It->second; + + Lock.unlock(); + llvm::Optional Command = + InnerCDB.getCompileCommand(File); + Lock.lock(); + return Cached.try_emplace(File, std::move(Command)).first->getValue(); +} + +tooling::CompileCommand +CachingCompilationDb::getFallbackCommand(PathRef File) const { + return InnerCDB.getFallbackCommand(File); +} + +void CachingCompilationDb::invalidate(PathRef File) { + std::unique_lock Lock(Mut); + Cached.erase(File); +} + +void CachingCompilationDb::clear() { + std::unique_lock Lock(Mut); + Cached.clear(); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/GlobalCompilationDatabase.h b/clangd/GlobalCompilationDatabase.h new file mode 100644 index 000000000..ab89a185b --- /dev/null +++ b/clangd/GlobalCompilationDatabase.h @@ -0,0 +1,119 @@ +//===--- 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 { + +class Logger; + +/// Provides compilation arguments used for parsing C and C++ files. +class GlobalCompilationDatabase { +public: + virtual ~GlobalCompilationDatabase() = default; + + /// If there are any known-good commands for building this file, returns one. + virtual llvm::Optional + getCompileCommand(PathRef File) const = 0; + + /// Makes a guess at how to build a file. + /// The default implementation just runs clang on the file. + /// Clangd should treat the results as unreliable. + virtual tooling::CompileCommand getFallbackCommand(PathRef File) const; + + /// 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: + DirectoryBasedGlobalCompilationDatabase( + llvm::Optional CompileCommandsDir); + ~DirectoryBasedGlobalCompilationDatabase() override; + + /// Scans File's parents looking for compilation databases. + /// Any extra flags will be added. + llvm::Optional + getCompileCommand(PathRef File) const override; + + /// Uses the default fallback command, adding any extra flags. + tooling::CompileCommand getFallbackCommand(PathRef File) const override; + + /// Set the compile commands directory to \p P. + void setCompileCommandsDir(Path P); + + /// Sets the extra flags that should be added to a file. + void setExtraFlagsForFile(PathRef File, std::vector ExtraFlags); + +private: + tooling::CompilationDatabase *getCDBForFile(PathRef File) const; + tooling::CompilationDatabase *getCDBInDirLocked(PathRef File) const; + void addExtraFlags(PathRef File, tooling::CompileCommand &C) const; + + mutable std::mutex Mutex; + /// Caches compilation databases loaded from directories(keys are + /// directories). + mutable llvm::StringMap> + CompilationDatabases; + + /// Stores extra flags per file. + llvm::StringMap> ExtraFlagsForFile; + /// Used for command argument pointing to folder where compile_commands.json + /// is located. + llvm::Optional CompileCommandsDir; +}; + +/// A wrapper around GlobalCompilationDatabase that caches the compile commands. +/// Note that only results of getCompileCommand are cached. +class CachingCompilationDb : public GlobalCompilationDatabase { +public: + explicit CachingCompilationDb(const GlobalCompilationDatabase &InnerCDB); + + /// Gets compile command for \p File from cache or CDB if it's not in the + /// cache. + llvm::Optional + getCompileCommand(PathRef File) const override; + + /// Forwards to the inner CDB. Results of this function are not cached. + tooling::CompileCommand getFallbackCommand(PathRef File) const override; + + /// Removes an entry for \p File if it's present in the cache. + void invalidate(PathRef File); + + /// Removes all cached compile commands. + void clear(); + +private: + const GlobalCompilationDatabase &InnerCDB; + mutable std::mutex Mut; + mutable llvm::StringMap> + Cached; /* GUARDED_BY(Mut) */ +}; + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Headers.cpp b/clangd/Headers.cpp new file mode 100644 index 000000000..137b29aa1 --- /dev/null +++ b/clangd/Headers.cpp @@ -0,0 +1,173 @@ +//===--- Headers.cpp - Include headers ---------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Headers.h" +#include "Compiler.h" +#include "Logger.h" +#include "SourceCode.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Lex/HeaderSearch.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace clangd { +namespace { + +class RecordHeaders : public PPCallbacks { +public: + RecordHeaders(const SourceManager &SM, IncludeStructure *Out) + : SM(SM), Out(Out) {} + + // Record existing #includes - both written and resolved paths. Only #includes + // in the main file are collected. + void InclusionDirective(SourceLocation HashLoc, const Token & /*IncludeTok*/, + llvm::StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + llvm::StringRef /*SearchPath*/, + llvm::StringRef /*RelativePath*/, + const Module * /*Imported*/, + SrcMgr::CharacteristicKind /*FileType*/) override { + if (SM.isInMainFile(HashLoc)) + Out->MainFileIncludes.push_back({ + halfOpenToRange(SM, FilenameRange), + (IsAngled ? "<" + FileName + ">" : "\"" + FileName + "\"").str(), + File ? File->tryGetRealPathName() : "", + }); + if (File) { + auto *IncludingFileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc)); + if (!IncludingFileEntry) { + assert(SM.getBufferName(HashLoc).startswith("<") && + "Expected #include location to be a file or "); + // Treat as if included from the main file. + IncludingFileEntry = SM.getFileEntryForID(SM.getMainFileID()); + } + Out->recordInclude(IncludingFileEntry->getName(), File->getName(), + File->tryGetRealPathName()); + } + } + +private: + const SourceManager &SM; + IncludeStructure *Out; +}; + +} // namespace + +bool isLiteralInclude(llvm::StringRef Include) { + return Include.startswith("<") || Include.startswith("\""); +} + +bool HeaderFile::valid() const { + return (Verbatim && isLiteralInclude(File)) || + (!Verbatim && llvm::sys::path::is_absolute(File)); +} + +std::unique_ptr +collectIncludeStructureCallback(const SourceManager &SM, + IncludeStructure *Out) { + return llvm::make_unique(SM, Out); +} + +void IncludeStructure::recordInclude(llvm::StringRef IncludingName, + llvm::StringRef IncludedName, + llvm::StringRef IncludedRealName) { + auto Child = fileIndex(IncludedName); + if (!IncludedRealName.empty() && RealPathNames[Child].empty()) + RealPathNames[Child] = IncludedRealName; + auto Parent = fileIndex(IncludingName); + IncludeChildren[Parent].push_back(Child); +} + +unsigned IncludeStructure::fileIndex(llvm::StringRef Name) { + auto R = NameToIndex.try_emplace(Name, RealPathNames.size()); + if (R.second) + RealPathNames.emplace_back(); + return R.first->getValue(); +} + +llvm::StringMap +IncludeStructure::includeDepth(llvm::StringRef Root) const { + // Include depth 0 is the main file only. + llvm::StringMap Result; + Result[Root] = 0; + std::vector CurrentLevel; + llvm::DenseSet Seen; + auto It = NameToIndex.find(Root); + if (It != NameToIndex.end()) { + CurrentLevel.push_back(It->second); + Seen.insert(It->second); + } + + // Each round of BFS traversal finds the next depth level. + std::vector PreviousLevel; + for (unsigned Level = 1; !CurrentLevel.empty(); ++Level) { + PreviousLevel.clear(); + PreviousLevel.swap(CurrentLevel); + for (const auto &Parent : PreviousLevel) { + for (const auto &Child : IncludeChildren.lookup(Parent)) { + if (Seen.insert(Child).second) { + CurrentLevel.push_back(Child); + const auto &Name = RealPathNames[Child]; + // Can't include files if we don't have their real path. + if (!Name.empty()) + Result[Name] = Level; + } + } + } + } + return Result; +} + +/// FIXME(ioeric): we might not want to insert an absolute include path if the +/// path is not shortened. +bool IncludeInserter::shouldInsertInclude( + const HeaderFile &DeclaringHeader, const HeaderFile &InsertedHeader) const { + assert(DeclaringHeader.valid() && InsertedHeader.valid()); + if (FileName == DeclaringHeader.File || FileName == InsertedHeader.File) + return false; + llvm::StringSet<> IncludedHeaders; + for (const auto &Inc : Inclusions) { + IncludedHeaders.insert(Inc.Written); + if (!Inc.Resolved.empty()) + IncludedHeaders.insert(Inc.Resolved); + } + auto Included = [&](llvm::StringRef Header) { + return IncludedHeaders.find(Header) != IncludedHeaders.end(); + }; + return !Included(DeclaringHeader.File) && !Included(InsertedHeader.File); +} + +std::string +IncludeInserter::calculateIncludePath(const HeaderFile &DeclaringHeader, + const HeaderFile &InsertedHeader) const { + assert(DeclaringHeader.valid() && InsertedHeader.valid()); + if (InsertedHeader.Verbatim) + return InsertedHeader.File; + bool IsSystem = false; + std::string Suggested = HeaderSearchInfo.suggestPathToFileForDiagnostics( + InsertedHeader.File, BuildDir, &IsSystem); + if (IsSystem) + Suggested = "<" + Suggested + ">"; + else + Suggested = "\"" + Suggested + "\""; + return Suggested; +} + +Optional IncludeInserter::insert(StringRef VerbatimHeader) const { + Optional Edit = None; + if (auto Insertion = Inserter.insert(VerbatimHeader.trim("\"<>"), + VerbatimHeader.startswith("<"))) + Edit = replacementToEdit(Code, *Insertion); + return Edit; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Headers.h b/clangd/Headers.h new file mode 100644 index 000000000..2abf27c60 --- /dev/null +++ b/clangd/Headers.h @@ -0,0 +1,144 @@ +//===--- Headers.h - Include headers -----------------------------*- 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_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H + +#include "Path.h" +#include "Protocol.h" +#include "SourceCode.h" +#include "clang/Basic/VirtualFileSystem.h" +#include "clang/Format/Format.h" +#include "clang/Lex/HeaderSearch.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Tooling/Inclusions/HeaderIncludes.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { + +/// Returns true if \p Include is literal include like "path" or . +bool isLiteralInclude(llvm::StringRef Include); + +/// Represents a header file to be #include'd. +struct HeaderFile { + std::string File; + /// If this is true, `File` is a literal string quoted with <> or "" that + /// can be #included directly; otherwise, `File` is an absolute file path. + bool Verbatim; + + bool valid() const; +}; + +// An #include directive that we found in the main file. +struct Inclusion { + Range R; // Inclusion range. + std::string Written; // Inclusion name as written e.g. . + Path Resolved; // Resolved path of included file. Empty if not resolved. +}; + +// Information captured about the inclusion graph in a translation unit. +// This includes detailed information about the direct #includes, and summary +// information about all transitive includes. +// +// It should be built incrementally with collectIncludeStructureCallback(). +// When we build the preamble, we capture and store its include structure along +// with the preamble data. When we use the preamble, we can copy its +// IncludeStructure and use another collectIncludeStructureCallback() to fill +// in any non-preamble inclusions. +class IncludeStructure { +public: + std::vector MainFileIncludes; + + // Return all transitively reachable files, and their minimum include depth. + // All transitive includes (absolute paths), with their minimum include depth. + // Root --> 0, #included file --> 1, etc. + // Root is clang's name for a file, which may not be absolute. + // Usually it should be SM.getFileEntryForID(SM.getMainFileID())->getName(). + llvm::StringMap includeDepth(llvm::StringRef Root) const; + + // This updates IncludeDepth(), but not MainFileIncludes. + void recordInclude(llvm::StringRef IncludingName, + llvm::StringRef IncludedName, + llvm::StringRef IncludedRealName); + +private: + // Identifying files in a way that persists from preamble build to subsequent + // builds is surprisingly hard. FileID is unavailable in InclusionDirective(), + // and RealPathName and UniqueID are not preseved in the preamble. + // We use the FileEntry::Name, which is stable, interned into a "file index". + // The paths we want to expose are the RealPathName, so store those too. + std::vector RealPathNames; // In file index order. + unsigned fileIndex(llvm::StringRef Name); + llvm::StringMap NameToIndex; // Values are file indexes. + // Maps a file's index to that of the files it includes. + llvm::DenseMap> IncludeChildren; +}; + +/// Returns a PPCallback that visits all inclusions in the main file. +std::unique_ptr +collectIncludeStructureCallback(const SourceManager &SM, IncludeStructure *Out); + +// Calculates insertion edit for including a new header in a file. +class IncludeInserter { +public: + IncludeInserter(StringRef FileName, StringRef Code, + const format::FormatStyle &Style, StringRef BuildDir, + HeaderSearch &HeaderSearchInfo) + : FileName(FileName), Code(Code), BuildDir(BuildDir), + HeaderSearchInfo(HeaderSearchInfo), + Inserter(FileName, Code, Style.IncludeStyle) {} + + void addExisting(Inclusion Inc) { Inclusions.push_back(std::move(Inc)); } + + /// Checks whether to add an #include of the header into \p File. + /// An #include will not be added if: + /// - Either \p DeclaringHeader or \p InsertedHeader is already (directly) + /// in \p Inclusions (including those included via different paths). + /// - \p DeclaringHeader or \p InsertedHeader is the same as \p File. + /// + /// \param DeclaringHeader is the original header corresponding to \p + /// InsertedHeader e.g. the header that declares a symbol. + /// \param InsertedHeader The preferred header to be inserted. This could be + /// the same as DeclaringHeader but must be provided. + bool shouldInsertInclude(const HeaderFile &DeclaringHeader, + const HeaderFile &InsertedHeader) const; + + /// Determines the preferred way to #include a file, taking into account the + /// search path. Usually this will prefer a shorter representation like + /// 'Foo/Bar.h' over a longer one like 'Baz/include/Foo/Bar.h'. + /// + /// \param DeclaringHeader is the original header corresponding to \p + /// InsertedHeader e.g. the header that declares a symbol. + /// \param InsertedHeader The preferred header to be inserted. This could be + /// the same as DeclaringHeader but must be provided. + /// + /// \return A quoted "path" or to be included. + std::string calculateIncludePath(const HeaderFile &DeclaringHeader, + const HeaderFile &InsertedHeader) const; + + /// Calculates an edit that inserts \p VerbatimHeader into code. If the header + /// is already included, this returns None. + llvm::Optional insert(llvm::StringRef VerbatimHeader) const; + +private: + StringRef FileName; + StringRef Code; + StringRef BuildDir; + HeaderSearch &HeaderSearchInfo; + std::vector Inclusions; + tooling::HeaderIncludes Inserter; // Computers insertion replacement. +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_HEADERS_H diff --git a/clangd/JSONRPCDispatcher.cpp b/clangd/JSONRPCDispatcher.cpp new file mode 100644 index 000000000..2741c6650 --- /dev/null +++ b/clangd/JSONRPCDispatcher.cpp @@ -0,0 +1,368 @@ +//===--- 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 "Trace.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/SourceMgr.h" +#include + +using namespace llvm; +using namespace clang; +using namespace clangd; + +namespace { +static Key RequestID; +static Key RequestOut; + +// When tracing, we trace a request and attach the repsonse in reply(). +// Because the Span isn't available, we find the current request using Context. +class RequestSpan { + RequestSpan(llvm::json::Object *Args) : Args(Args) {} + std::mutex Mu; + llvm::json::Object *Args; + static Key> RSKey; + +public: + // Return a context that's aware of the enclosing request, identified by Span. + static Context stash(const trace::Span &Span) { + return Context::current().derive( + RSKey, std::unique_ptr(new RequestSpan(Span.Args))); + } + + // If there's an enclosing request and the tracer is interested, calls \p F + // with a json::Object where request info can be added. + template static void attach(Func &&F) { + auto *RequestArgs = Context::current().get(RSKey); + if (!RequestArgs || !*RequestArgs || !(*RequestArgs)->Args) + return; + std::lock_guard Lock((*RequestArgs)->Mu); + F(*(*RequestArgs)->Args); + } +}; +Key> RequestSpan::RSKey; +} // namespace + +void JSONOutput::writeMessage(const json::Value &Message) { + std::string S; + llvm::raw_string_ostream OS(S); + if (Pretty) + OS << llvm::formatv("{0:2}", Message); + else + OS << Message; + OS.flush(); + + { + std::lock_guard Guard(StreamMutex); + Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S; + Outs.flush(); + } + vlog(">>> {0}\n", S); +} + +void JSONOutput::log(Logger::Level Level, + const llvm::formatv_object_base &Message) { + if (Level < MinLevel) + return; + llvm::sys::TimePoint<> Timestamp = std::chrono::system_clock::now(); + trace::log(Message); + std::lock_guard Guard(StreamMutex); + Logs << llvm::formatv("{0}[{1:%H:%M:%S.%L}] {2}\n", indicator(Level), + Timestamp, Message); + Logs.flush(); +} + +void JSONOutput::mirrorInput(const Twine &Message) { + if (!InputMirror) + return; + + *InputMirror << Message; + InputMirror->flush(); +} + +void clangd::reply(json::Value &&Result) { + auto ID = Context::current().get(RequestID); + if (!ID) { + elog("Attempted to reply to a notification!"); + return; + } + RequestSpan::attach([&](json::Object &Args) { Args["Reply"] = Result; }); + log("--> reply({0})", *ID); + Context::current() + .getExisting(RequestOut) + ->writeMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"id", *ID}, + {"result", std::move(Result)}, + }); +} + +void clangd::replyError(ErrorCode Code, const llvm::StringRef &Message) { + elog("Error {0}: {1}", static_cast(Code), Message); + RequestSpan::attach([&](json::Object &Args) { + Args["Error"] = json::Object{{"code", static_cast(Code)}, + {"message", Message.str()}}; + }); + + if (auto ID = Context::current().get(RequestID)) { + log("--> reply({0}) error: {1}", *ID, Message); + Context::current() + .getExisting(RequestOut) + ->writeMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"id", *ID}, + {"error", json::Object{{"code", static_cast(Code)}, + {"message", Message}}}, + }); + } +} + +void clangd::call(StringRef Method, json::Value &&Params) { + RequestSpan::attach([&](json::Object &Args) { + Args["Call"] = json::Object{{"method", Method.str()}, {"params", Params}}; + }); + // FIXME: Generate/Increment IDs for every request so that we can get proper + // replies once we need to. + auto ID = 1; + log("--> {0}({1})", Method, ID); + Context::current() + .getExisting(RequestOut) + ->writeMessage(json::Object{ + {"jsonrpc", "2.0"}, + {"id", ID}, + {"method", Method}, + {"params", std::move(Params)}, + }); +} + +void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) { + assert(!Handlers.count(Method) && "Handler already registered!"); + Handlers[Method] = std::move(H); +} + +static void logIncomingMessage(const llvm::Optional &ID, + llvm::Optional Method, + const json::Object *Error) { + if (Method) { // incoming request + if (ID) // call + log("<-- {0}({1})", *Method, *ID); + else // notification + log("<-- {0}", *Method); + } else if (ID) { // response, ID must be provided + if (Error) + log("<-- reply({0}) error: {1}", *ID, + Error->getString("message").getValueOr("")); + else + log("<-- reply({0})", *ID); + } +} + +bool JSONRPCDispatcher::call(const json::Value &Message, + JSONOutput &Out) const { + // Message must be an object with "jsonrpc":"2.0". + auto *Object = Message.getAsObject(); + if (!Object || Object->getString("jsonrpc") != Optional("2.0")) + return false; + // ID may be any JSON value. If absent, this is a notification. + llvm::Optional ID; + if (auto *I = Object->get("id")) + ID = std::move(*I); + auto Method = Object->getString("method"); + logIncomingMessage(ID, Method, Object->getObject("error")); + if (!Method) // We only handle incoming requests, and ignore responses. + return false; + // Params should be given, use null if not. + json::Value Params = nullptr; + if (auto *P = Object->get("params")) + Params = std::move(*P); + + auto I = Handlers.find(*Method); + auto &Handler = I != Handlers.end() ? I->second : UnknownHandler; + + // Create a Context that contains request information. + WithContextValue WithRequestOut(RequestOut, &Out); + llvm::Optional WithID; + if (ID) + WithID.emplace(RequestID, *ID); + + // Create a tracing Span covering the whole request lifetime. + trace::Span Tracer(*Method); + if (ID) + SPAN_ATTACH(Tracer, "ID", *ID); + SPAN_ATTACH(Tracer, "Params", Params); + + // Stash a reference to the span args, so later calls can add metadata. + WithContext WithRequestSpan(RequestSpan::stash(Tracer)); + Handler(std::move(Params)); + return true; +} + +// Tries to read a line up to and including \n. +// If failing, feof() or ferror() will be set. +static bool readLine(std::FILE *In, std::string &Out) { + static constexpr int BufSize = 1024; + size_t Size = 0; + Out.clear(); + for (;;) { + Out.resize(Size + BufSize); + // Handle EINTR which is sent when a debugger attaches on some platforms. + if (!llvm::sys::RetryAfterSignal(nullptr, ::fgets, &Out[Size], BufSize, In)) + return false; + clearerr(In); + // If the line contained null bytes, anything after it (including \n) will + // be ignored. Fortunately this is not a legal header or JSON. + size_t Read = std::strlen(&Out[Size]); + if (Read > 0 && Out[Size + Read - 1] == '\n') { + Out.resize(Size + Read); + return true; + } + Size += Read; + } +} + +// Returns None when: +// - ferror() or feof() are set. +// - Content-Length is missing or empty (protocol error) +static llvm::Optional readStandardMessage(std::FILE *In, + JSONOutput &Out) { + // A Language Server Protocol message starts with a set of HTTP headers, + // delimited by \r\n, and terminated by an empty line (\r\n). + unsigned long long ContentLength = 0; + std::string Line; + while (true) { + if (feof(In) || ferror(In) || !readLine(In, Line)) + return llvm::None; + + Out.mirrorInput(Line); + llvm::StringRef LineRef(Line); + + // We allow comments in headers. Technically this isn't part + // of the LSP specification, but makes writing tests easier. + if (LineRef.startswith("#")) + continue; + + // Content-Length is a mandatory header, and the only one we handle. + if (LineRef.consume_front("Content-Length: ")) { + if (ContentLength != 0) { + elog("Warning: Duplicate Content-Length header received. " + "The previous value for this message ({0}) was ignored.", + ContentLength); + } + llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength); + continue; + } else if (!LineRef.trim().empty()) { + // It's another header, ignore it. + continue; + } else { + // An empty line indicates the end of headers. + // Go ahead and read the JSON. + break; + } + } + + // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999" + if (ContentLength > 1 << 30) { // 1024M + elog("Refusing to read message with long Content-Length: {0}. " + "Expect protocol errors", + ContentLength); + return llvm::None; + } + if (ContentLength == 0) { + log("Warning: Missing Content-Length header, or zero-length message."); + return llvm::None; + } + + std::string JSON(ContentLength, '\0'); + for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) { + // Handle EINTR which is sent when a debugger attaches on some platforms. + Read = llvm::sys::RetryAfterSignal(0u, ::fread, &JSON[Pos], 1, + ContentLength - Pos, In); + Out.mirrorInput(StringRef(&JSON[Pos], Read)); + if (Read == 0) { + elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos, + ContentLength); + return llvm::None; + } + clearerr(In); // If we're done, the error was transient. If we're not done, + // either it was transient or we'll see it again on retry. + Pos += Read; + } + return std::move(JSON); +} + +// For lit tests we support a simplified syntax: +// - messages are delimited by '---' on a line by itself +// - lines starting with # are ignored. +// This is a testing path, so favor simplicity over performance here. +// When returning None, feof() or ferror() will be set. +static llvm::Optional readDelimitedMessage(std::FILE *In, + JSONOutput &Out) { + std::string JSON; + std::string Line; + while (readLine(In, Line)) { + auto LineRef = llvm::StringRef(Line).trim(); + if (LineRef.startswith("#")) // comment + continue; + + // found a delimiter + if (LineRef.rtrim() == "---") + break; + + JSON += Line; + } + + if (ferror(In)) { + elog("Input error while reading message!"); + return llvm::None; + } else { // Including EOF + Out.mirrorInput( + llvm::formatv("Content-Length: {0}\r\n\r\n{1}", JSON.size(), JSON)); + return std::move(JSON); + } +} + +// The use of C-style std::FILE* IO deserves some explanation. +// Previously, std::istream was used. When a debugger attached on MacOS, the +// process received EINTR, the stream went bad, and clangd exited. +// A retry-on-EINTR loop around reads solved this problem, but caused clangd to +// sometimes hang rather than exit on other OSes. The interaction between +// istreams and signals isn't well-specified, so it's hard to get this right. +// The C APIs seem to be clearer in this respect. +void clangd::runLanguageServerLoop(std::FILE *In, JSONOutput &Out, + JSONStreamStyle InputStyle, + JSONRPCDispatcher &Dispatcher, + bool &IsDone) { + auto &ReadMessage = + (InputStyle == Delimited) ? readDelimitedMessage : readStandardMessage; + while (!IsDone && !feof(In)) { + if (ferror(In)) { + elog("IO error: {0}", llvm::sys::StrError()); + return; + } + if (auto JSON = ReadMessage(In, Out)) { + if (auto Doc = json::parse(*JSON)) { + // Log the formatted message. + vlog(Out.Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc); + // Finally, execute the action for this JSON message. + if (!Dispatcher.call(*Doc, Out)) + elog("JSON dispatch failed!"); + } else { + // Parse error. Log the raw message. + vlog("<<< {0}\n", *JSON); + elog("JSON parse error: {0}", llvm::toString(Doc.takeError())); + } + } + } +} diff --git a/clangd/JSONRPCDispatcher.h b/clangd/JSONRPCDispatcher.h new file mode 100644 index 000000000..e8c96fc92 --- /dev/null +++ b/clangd/JSONRPCDispatcher.h @@ -0,0 +1,117 @@ +//===--- 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 "Logger.h" +#include "Protocol.h" +#include "Trace.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/JSON.h" +#include +#include + +namespace clang { +namespace clangd { + +/// Encapsulates output and logs streams and provides thread-safe access to +/// them. +class JSONOutput : public Logger { + // FIXME(ibiryukov): figure out if we can shrink the public interface of + // JSONOutput now that we pass Context everywhere. +public: + JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs, + Logger::Level MinLevel, llvm::raw_ostream *InputMirror = nullptr, + bool Pretty = false) + : Pretty(Pretty), MinLevel(MinLevel), Outs(Outs), Logs(Logs), + InputMirror(InputMirror) {} + + /// Emit a JSONRPC message. + void writeMessage(const llvm::json::Value &Result); + + /// Write a line to the logging stream. + void log(Level, const llvm::formatv_object_base &Message) override; + + /// Mirror \p Message into InputMirror stream. Does nothing if InputMirror is + /// null. + /// Unlike other methods of JSONOutput, mirrorInput is not thread-safe. + void mirrorInput(const Twine &Message); + + // Whether output should be pretty-printed. + const bool Pretty; + +private: + Logger::Level MinLevel; + llvm::raw_ostream &Outs; + llvm::raw_ostream &Logs; + llvm::raw_ostream *InputMirror; + + std::mutex StreamMutex; +}; + +/// Sends a successful reply. +/// Current context must derive from JSONRPCDispatcher::Handler. +void reply(llvm::json::Value &&Result); +/// Sends an error response to the client, and logs it. +/// Current context must derive from JSONRPCDispatcher::Handler. +void replyError(ErrorCode Code, const llvm::StringRef &Message); +/// Sends a request to the client. +/// Current context must derive from JSONRPCDispatcher::Handler. +void call(llvm::StringRef Method, llvm::json::Value &&Params); + +/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the +/// registered Handler for the method received. +class JSONRPCDispatcher { +public: + // A handler responds to requests for a particular method name. + using Handler = std::function; + + /// Create a new JSONRPCDispatcher. UnknownHandler is called when an unknown + /// method is received. + JSONRPCDispatcher(Handler UnknownHandler) + : UnknownHandler(std::move(UnknownHandler)) {} + + /// Registers a Handler for the specified Method. + void registerHandler(StringRef Method, Handler H); + + /// Parses a JSONRPC message and calls the Handler for it. + bool call(const llvm::json::Value &Message, JSONOutput &Out) const; + +private: + llvm::StringMap Handlers; + Handler UnknownHandler; +}; + +/// Controls the way JSON-RPC messages are encoded (both input and output). +enum JSONStreamStyle { + /// Encoding per the LSP specification, with mandatory Content-Length header. + Standard, + /// Messages are delimited by a '---' line. Comment lines start with #. + Delimited +}; + +/// 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. +/// We use C-style FILE* for reading as std::istream has unclear interaction +/// with signals, which are sent by debuggers on some OSs. +void runLanguageServerLoop(std::FILE *In, JSONOutput &Out, + JSONStreamStyle InputStyle, + JSONRPCDispatcher &Dispatcher, bool &IsDone); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Logger.cpp b/clangd/Logger.cpp new file mode 100644 index 000000000..5ce3351f1 --- /dev/null +++ b/clangd/Logger.cpp @@ -0,0 +1,48 @@ +//===--- Logger.cpp - Logger interface for clangd -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Logger.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace clang { +namespace clangd { + +namespace { +Logger *L = nullptr; +} // namespace + +LoggingSession::LoggingSession(clangd::Logger &Instance) { + assert(!L); + L = &Instance; +} + +LoggingSession::~LoggingSession() { L = nullptr; } + +void detail::log(Logger::Level Level, + const llvm::formatv_object_base &Message) { + if (L) + L->log(Level, Message); + else { + static std::mutex Mu; + std::lock_guard Guard(Mu); + llvm::errs() << Message << "\n"; + } +} + +const char *detail::debugType(const char *Filename) { + if (const char *Slash = strrchr(Filename, '/')) + return Slash + 1; + if (const char *Backslash = strrchr(Filename, '\\')) + return Backslash + 1; + return Filename; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Logger.h b/clangd/Logger.h new file mode 100644 index 000000000..cc6e3a0a7 --- /dev/null +++ b/clangd/Logger.h @@ -0,0 +1,92 @@ +//===--- Logger.h - Logger interface for clangd ------------------*- 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_LOGGER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_LOGGER_H + +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatAdapters.h" +#include "llvm/Support/FormatVariadic.h" + +namespace clang { +namespace clangd { + +/// Interface to allow custom logging in clangd. +class Logger { +public: + virtual ~Logger() = default; + + enum Level { Debug, Verbose, Info, Error }; + static char indicator(Level L) { return "DVIE"[L]; } + + /// Implementations of this method must be thread-safe. + virtual void log(Level, const llvm::formatv_object_base &Message) = 0; +}; + +namespace detail { +const char *debugType(const char *Filename); +void log(Logger::Level, const llvm::formatv_object_base &); + +// We often want to consume llvm::Errors by value when passing them to log(). +// We automatically wrap them in llvm::fmt_consume() as formatv requires. +template T &&wrap(T &&V) { return std::forward(V); } +inline decltype(fmt_consume(llvm::Error::success())) wrap(llvm::Error &&V) { + return fmt_consume(std::move(V)); +} +template +void log(Logger::Level L, const char *Fmt, Ts &&... Vals) { + detail::log(L, llvm::formatv(Fmt, detail::wrap(std::forward(Vals))...)); +} +} // namespace detail + +// Clangd logging functions write to a global logger set by LoggingSession. +// If no logger is registered, writes to llvm::errs(). +// All accept llvm::formatv()-style arguments, e.g. log("Text={0}", Text). + +// elog() is used for "loud" errors and warnings. +// This level is often visible to users. +template void elog(const char *Fmt, Ts &&... Vals) { + detail::log(Logger::Error, Fmt, std::forward(Vals)...); +} +// log() is used for information important to understanding a clangd session. +// e.g. the names of LSP messages sent are logged at this level. +// This level could be enabled in production builds to allow later inspection. +template void log(const char *Fmt, Ts &&... Vals) { + detail::log(Logger::Info, Fmt, std::forward(Vals)...); +} +// vlog() is used for details often needed for debugging clangd sessions. +// This level would typically be enabled for clangd developers. +template void vlog(const char *Fmt, Ts &&... Vals) { + detail::log(Logger::Verbose, Fmt, std::forward(Vals)...); +} +// dlog only logs if --debug was passed, or --debug_only=Basename. +// This level would be enabled in a targeted way when debugging. +#define dlog(...) \ + DEBUG_WITH_TYPE(::clang::clangd::detail::debugType(__FILE__), \ + ::clang::clangd::detail::log(Logger::Debug, __VA_ARGS__)) + +/// Only one LoggingSession can be active at a time. +class LoggingSession { +public: + LoggingSession(clangd::Logger &Instance); + ~LoggingSession(); + + LoggingSession(LoggingSession &&) = delete; + LoggingSession &operator=(LoggingSession &&) = delete; + + LoggingSession(LoggingSession const &) = delete; + LoggingSession &operator=(LoggingSession const &) = delete; +}; + +} // 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..5ecd7195c --- /dev/null +++ b/clangd/Protocol.cpp @@ -0,0 +1,601 @@ +//===--- 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. +// +//===----------------------------------------------------------------------===// + +#include "Protocol.h" +#include "Logger.h" +#include "URI.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +URIForFile::URIForFile(std::string AbsPath) { + assert(llvm::sys::path::is_absolute(AbsPath) && "the path is relative"); + File = std::move(AbsPath); +} + +bool fromJSON(const json::Value &E, URIForFile &R) { + if (auto S = E.getAsString()) { + auto U = URI::parse(*S); + if (!U) { + elog("Failed to parse URI {0}: {1}", *S, U.takeError()); + return false; + } + if (U->scheme() != "file" && U->scheme() != "test") { + elog("Clangd only supports 'file' URI scheme for workspace files: {0}", + *S); + return false; + } + auto Path = URI::resolve(*U); + if (!Path) { + log("{0}", Path.takeError()); + return false; + } + R = URIForFile(*Path); + return true; + } + return false; +} + +json::Value toJSON(const URIForFile &U) { return U.uri(); } + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const URIForFile &U) { + return OS << U.uri(); +} + +json::Value toJSON(const TextDocumentIdentifier &R) { + return json::Object{{"uri", R.uri}}; +} + +bool fromJSON(const json::Value &Params, TextDocumentIdentifier &R) { + json::ObjectMapper O(Params); + return O && O.map("uri", R.uri); +} + +bool fromJSON(const json::Value &Params, Position &R) { + json::ObjectMapper O(Params); + return O && O.map("line", R.line) && O.map("character", R.character); +} + +json::Value toJSON(const Position &P) { + return json::Object{ + {"line", P.line}, + {"character", P.character}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Position &P) { + return OS << P.line << ':' << P.character; +} + +bool fromJSON(const json::Value &Params, Range &R) { + json::ObjectMapper O(Params); + return O && O.map("start", R.start) && O.map("end", R.end); +} + +json::Value toJSON(const Range &P) { + return json::Object{ + {"start", P.start}, + {"end", P.end}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Range &R) { + return OS << R.start << '-' << R.end; +} + +json::Value toJSON(const Location &P) { + return json::Object{ + {"uri", P.uri}, + {"range", P.range}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Location &L) { + return OS << L.range << '@' << L.uri; +} + +bool fromJSON(const json::Value &Params, TextDocumentItem &R) { + json::ObjectMapper O(Params); + return O && O.map("uri", R.uri) && O.map("languageId", R.languageId) && + O.map("version", R.version) && O.map("text", R.text); +} + +bool fromJSON(const json::Value &Params, Metadata &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("extraFlags", R.extraFlags); + return true; +} + +bool fromJSON(const json::Value &Params, TextEdit &R) { + json::ObjectMapper O(Params); + return O && O.map("range", R.range) && O.map("newText", R.newText); +} + +json::Value toJSON(const TextEdit &P) { + return json::Object{ + {"range", P.range}, + {"newText", P.newText}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const TextEdit &TE) { + OS << TE.range << " => \""; + printEscapedString(TE.newText, OS); + return OS << '"'; +} + +bool fromJSON(const json::Value &E, TraceLevel &Out) { + if (auto S = E.getAsString()) { + if (*S == "off") { + Out = TraceLevel::Off; + return true; + } else if (*S == "messages") { + Out = TraceLevel::Messages; + return true; + } else if (*S == "verbose") { + Out = TraceLevel::Verbose; + return true; + } + } + return false; +} + +bool fromJSON(const json::Value &Params, CompletionItemClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("snippetSupport", R.snippetSupport); + O.map("commitCharacterSupport", R.commitCharacterSupport); + return true; +} + +bool fromJSON(const json::Value &Params, CompletionClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("dynamicRegistration", R.dynamicRegistration); + O.map("completionItem", R.completionItem); + O.map("contextSupport", R.contextSupport); + return true; +} + +bool fromJSON(const json::Value &E, SymbolKind &Out) { + if (auto T = E.getAsInteger()) { + if (*T < static_cast(SymbolKind::File) || + *T > static_cast(SymbolKind::TypeParameter)) + return false; + Out = static_cast(*T); + return true; + } + return false; +} + +bool fromJSON(const json::Value &E, std::vector &Out) { + if (auto *A = E.getAsArray()) { + Out.clear(); + for (size_t I = 0; I < A->size(); ++I) { + SymbolKind KindOut; + if (fromJSON((*A)[I], KindOut)) + Out.push_back(KindOut); + } + return true; + } + return false; +} + +bool fromJSON(const json::Value &Params, SymbolKindCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("valueSet", R.valueSet); +} + +SymbolKind adjustKindToCapability(SymbolKind Kind, + SymbolKindBitset &SupportedSymbolKinds) { + auto KindVal = static_cast(Kind); + if (KindVal >= SymbolKindMin && KindVal <= SupportedSymbolKinds.size() && + SupportedSymbolKinds[KindVal]) + return Kind; + + switch (Kind) { + // Provide some fall backs for common kinds that are close enough. + case SymbolKind::Struct: + return SymbolKind::Class; + case SymbolKind::EnumMember: + return SymbolKind::Enum; + default: + return SymbolKind::String; + } +} + +bool fromJSON(const json::Value &Params, WorkspaceSymbolCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("symbolKind", R.symbolKind); +} + +bool fromJSON(const json::Value &Params, WorkspaceClientCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("symbol", R.symbol); +} + +bool fromJSON(const json::Value &Params, TextDocumentClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("completion", R.completion); + return true; +} + +bool fromJSON(const json::Value &Params, ClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("textDocument", R.textDocument); + O.map("workspace", R.workspace); + return true; +} + +bool fromJSON(const json::Value &Params, InitializeParams &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + // We deliberately don't fail if we can't parse individual fields. + // Failing to handle a slightly malformed initialize would be a disaster. + O.map("processId", R.processId); + O.map("rootUri", R.rootUri); + O.map("rootPath", R.rootPath); + O.map("capabilities", R.capabilities); + O.map("trace", R.trace); + O.map("initializationOptions", R.initializationOptions); + return true; +} + +bool fromJSON(const json::Value &Params, DidOpenTextDocumentParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("metadata", R.metadata); +} + +bool fromJSON(const json::Value &Params, DidCloseTextDocumentParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + +bool fromJSON(const json::Value &Params, DidChangeTextDocumentParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("contentChanges", R.contentChanges) && + O.map("wantDiagnostics", R.wantDiagnostics); +} + +bool fromJSON(const json::Value &E, FileChangeType &Out) { + if (auto T = E.getAsInteger()) { + if (*T < static_cast(FileChangeType::Created) || + *T > static_cast(FileChangeType::Deleted)) + return false; + Out = static_cast(*T); + return true; + } + return false; +} + +bool fromJSON(const json::Value &Params, FileEvent &R) { + json::ObjectMapper O(Params); + return O && O.map("uri", R.uri) && O.map("type", R.type); +} + +bool fromJSON(const json::Value &Params, DidChangeWatchedFilesParams &R) { + json::ObjectMapper O(Params); + return O && O.map("changes", R.changes); +} + +bool fromJSON(const json::Value &Params, TextDocumentContentChangeEvent &R) { + json::ObjectMapper O(Params); + return O && O.map("range", R.range) && O.map("rangeLength", R.rangeLength) && + O.map("text", R.text); +} + +bool fromJSON(const json::Value &Params, FormattingOptions &R) { + json::ObjectMapper O(Params); + return O && O.map("tabSize", R.tabSize) && + O.map("insertSpaces", R.insertSpaces); +} + +json::Value toJSON(const FormattingOptions &P) { + return json::Object{ + {"tabSize", P.tabSize}, + {"insertSpaces", P.insertSpaces}, + }; +} + +bool fromJSON(const json::Value &Params, DocumentRangeFormattingParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("range", R.range) && O.map("options", R.options); +} + +bool fromJSON(const json::Value &Params, DocumentOnTypeFormattingParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("position", R.position) && O.map("ch", R.ch) && + O.map("options", R.options); +} + +bool fromJSON(const json::Value &Params, DocumentFormattingParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("options", R.options); +} + +bool fromJSON(const json::Value &Params, DocumentSymbolParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + +bool fromJSON(const json::Value &Params, Diagnostic &R) { + json::ObjectMapper O(Params); + if (!O || !O.map("range", R.range) || !O.map("message", R.message)) + return false; + O.map("severity", R.severity); + return true; +} + +bool fromJSON(const json::Value &Params, CodeActionContext &R) { + json::ObjectMapper O(Params); + return O && O.map("diagnostics", R.diagnostics); +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diagnostic &D) { + OS << D.range << " ["; + switch (D.severity) { + case 1: + OS << "error"; + break; + case 2: + OS << "warning"; + break; + case 3: + OS << "note"; + break; + case 4: + OS << "remark"; + break; + default: + OS << "diagnostic"; + break; + } + return OS << '(' << D.severity << "): " << D.message << "]"; +} + +bool fromJSON(const json::Value &Params, CodeActionParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("range", R.range) && O.map("context", R.context); +} + +bool fromJSON(const json::Value &Params, WorkspaceEdit &R) { + json::ObjectMapper O(Params); + return O && O.map("changes", R.changes); +} + +const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND = + "clangd.applyFix"; +bool fromJSON(const json::Value &Params, ExecuteCommandParams &R) { + json::ObjectMapper O(Params); + if (!O || !O.map("command", R.command)) + return false; + + auto Args = Params.getAsObject()->getArray("arguments"); + if (R.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) { + return Args && Args->size() == 1 && + fromJSON(Args->front(), R.workspaceEdit); + } + return false; // Unrecognized command. +} + +json::Value toJSON(const SymbolInformation &P) { + return json::Object{ + {"name", P.name}, + {"kind", static_cast(P.kind)}, + {"location", P.location}, + {"containerName", P.containerName}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const SymbolInformation &SI) { + O << SI.containerName << "::" << SI.name << " - " << toJSON(SI); + return O; +} + +bool fromJSON(const json::Value &Params, WorkspaceSymbolParams &R) { + json::ObjectMapper O(Params); + return O && O.map("query", R.query); +} + +json::Value toJSON(const Command &C) { + auto Cmd = json::Object{{"title", C.title}, {"command", C.command}}; + if (C.workspaceEdit) + Cmd["arguments"] = {*C.workspaceEdit}; + return std::move(Cmd); +} + +json::Value toJSON(const WorkspaceEdit &WE) { + if (!WE.changes) + return json::Object{}; + json::Object FileChanges; + for (auto &Change : *WE.changes) + FileChanges[Change.first] = json::Array(Change.second); + return json::Object{{"changes", std::move(FileChanges)}}; +} + +json::Value toJSON(const ApplyWorkspaceEditParams &Params) { + return json::Object{{"edit", Params.edit}}; +} + +bool fromJSON(const json::Value &Params, TextDocumentPositionParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("position", R.position); +} + +static StringRef toTextKind(MarkupKind Kind) { + switch (Kind) { + case MarkupKind::PlainText: + return "plaintext"; + case MarkupKind::Markdown: + return "markdown"; + } + llvm_unreachable("Invalid MarkupKind"); +} + +json::Value toJSON(const MarkupContent &MC) { + if (MC.value.empty()) + return nullptr; + + return json::Object{ + {"kind", toTextKind(MC.kind)}, + {"value", MC.value}, + }; +} + +json::Value toJSON(const Hover &H) { + json::Object Result{{"contents", toJSON(H.contents)}}; + + if (H.range.hasValue()) + Result["range"] = toJSON(*H.range); + + return std::move(Result); +} + +json::Value toJSON(const CompletionItem &CI) { + assert(!CI.label.empty() && "completion item label is required"); + json::Object Result{{"label", CI.label}}; + if (CI.kind != CompletionItemKind::Missing) + Result["kind"] = static_cast(CI.kind); + if (!CI.detail.empty()) + Result["detail"] = CI.detail; + if (!CI.documentation.empty()) + Result["documentation"] = CI.documentation; + if (!CI.sortText.empty()) + Result["sortText"] = CI.sortText; + if (!CI.filterText.empty()) + Result["filterText"] = CI.filterText; + if (!CI.insertText.empty()) + Result["insertText"] = CI.insertText; + if (CI.insertTextFormat != InsertTextFormat::Missing) + Result["insertTextFormat"] = static_cast(CI.insertTextFormat); + if (CI.textEdit) + Result["textEdit"] = *CI.textEdit; + if (!CI.additionalTextEdits.empty()) + Result["additionalTextEdits"] = json::Array(CI.additionalTextEdits); + return std::move(Result); +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const CompletionItem &I) { + O << I.label << " - " << toJSON(I); + return O; +} + +bool operator<(const CompletionItem &L, const CompletionItem &R) { + return (L.sortText.empty() ? L.label : L.sortText) < + (R.sortText.empty() ? R.label : R.sortText); +} + +json::Value toJSON(const CompletionList &L) { + return json::Object{ + {"isIncomplete", L.isIncomplete}, + {"items", json::Array(L.items)}, + }; +} + +json::Value toJSON(const ParameterInformation &PI) { + assert(!PI.label.empty() && "parameter information label is required"); + json::Object Result{{"label", PI.label}}; + if (!PI.documentation.empty()) + Result["documentation"] = PI.documentation; + return std::move(Result); +} + +json::Value toJSON(const SignatureInformation &SI) { + assert(!SI.label.empty() && "signature information label is required"); + json::Object Result{ + {"label", SI.label}, + {"parameters", json::Array(SI.parameters)}, + }; + if (!SI.documentation.empty()) + Result["documentation"] = SI.documentation; + return std::move(Result); +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const SignatureInformation &I) { + O << I.label << " - " << toJSON(I); + return O; +} + +json::Value toJSON(const SignatureHelp &SH) { + assert(SH.activeSignature >= 0 && + "Unexpected negative value for number of active signatures."); + assert(SH.activeParameter >= 0 && + "Unexpected negative value for active parameter index"); + return json::Object{ + {"activeSignature", SH.activeSignature}, + {"activeParameter", SH.activeParameter}, + {"signatures", json::Array(SH.signatures)}, + }; +} + +bool fromJSON(const json::Value &Params, RenameParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("position", R.position) && O.map("newName", R.newName); +} + +json::Value toJSON(const DocumentHighlight &DH) { + return json::Object{ + {"range", toJSON(DH.range)}, + {"kind", static_cast(DH.kind)}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const DocumentHighlight &V) { + O << V.range; + if (V.kind == DocumentHighlightKind::Read) + O << "(r)"; + if (V.kind == DocumentHighlightKind::Write) + O << "(w)"; + return O; +} + +bool fromJSON(const json::Value &Params, DidChangeConfigurationParams &CCP) { + json::ObjectMapper O(Params); + return O && O.map("settings", CCP.settings); +} + +bool fromJSON(const json::Value &Params, + ClangdConfigurationParamsChange &CCPC) { + json::ObjectMapper O(Params); + return O && O.map("compilationDatabasePath", CCPC.compilationDatabasePath); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Protocol.h b/clangd/Protocol.h new file mode 100644 index 000000000..7f33976de --- /dev/null +++ b/clangd/Protocol.h @@ -0,0 +1,844 @@ +//===--- 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 toJSON and fromJSON function, that converts between +// the struct and a JSON representation. (See JSON.h) +// +// Some structs also have operator<< serialization. This is for debugging and +// tests, and is not generally machine-readable. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H + +#include "URI.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/JSON.h" +#include +#include +#include + +namespace clang { +namespace clangd { + +enum class ErrorCode { + // Defined by JSON RPC. + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + // Defined by the protocol. + RequestCancelled = -32800, +}; + +struct URIForFile { + URIForFile() = default; + explicit URIForFile(std::string AbsPath); + + /// Retrieves absolute path to the file. + llvm::StringRef file() const { return File; } + + explicit operator bool() const { return !File.empty(); } + std::string uri() const { return URI::createFile(File).toString(); } + + friend bool operator==(const URIForFile &LHS, const URIForFile &RHS) { + return LHS.File == RHS.File; + } + + friend bool operator!=(const URIForFile &LHS, const URIForFile &RHS) { + return !(LHS == RHS); + } + + friend bool operator<(const URIForFile &LHS, const URIForFile &RHS) { + return LHS.File < RHS.File; + } + +private: + std::string File; +}; + +/// Serialize/deserialize \p URIForFile to/from a string URI. +llvm::json::Value toJSON(const URIForFile &U); +bool fromJSON(const llvm::json::Value &, URIForFile &); + +struct TextDocumentIdentifier { + /// The text document's URI. + URIForFile uri; +}; +llvm::json::Value toJSON(const TextDocumentIdentifier &); +bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &); + +struct Position { + /// Line position in a document (zero-based). + int line = 0; + + /// Character offset on a line in a document (zero-based). + /// WARNING: this is in UTF-16 codepoints, not bytes or characters! + /// Use the functions in SourceCode.h to construct/interpret Positions. + int character = 0; + + 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 !(LHS == RHS); + } + 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); + } +}; +bool fromJSON(const llvm::json::Value &, Position &); +llvm::json::Value toJSON(const Position &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Position &); + +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 !(LHS == RHS); + } + friend bool operator<(const Range &LHS, const Range &RHS) { + return std::tie(LHS.start, LHS.end) < std::tie(RHS.start, RHS.end); + } + + bool contains(Position Pos) const { return start <= Pos && Pos < end; } +}; +bool fromJSON(const llvm::json::Value &, Range &); +llvm::json::Value toJSON(const Range &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Range &); + +struct Location { + /// The text document's URI. + URIForFile 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); + } +}; +llvm::json::Value toJSON(const Location &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Location &); + +struct Metadata { + std::vector extraFlags; +}; +bool fromJSON(const llvm::json::Value &, Metadata &); + +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; +}; +bool fromJSON(const llvm::json::Value &, TextEdit &); +llvm::json::Value toJSON(const TextEdit &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &); + +struct TextDocumentItem { + /// The text document's URI. + URIForFile uri; + + /// The text document's language identifier. + std::string languageId; + + /// The version number of this document (it will strictly increase after each + int version = 0; + + /// The content of the opened text document. + std::string text; +}; +bool fromJSON(const llvm::json::Value &, TextDocumentItem &); + +enum class TraceLevel { + Off = 0, + Messages = 1, + Verbose = 2, +}; +bool fromJSON(const llvm::json::Value &E, TraceLevel &Out); + +struct NoParams {}; +inline bool fromJSON(const llvm::json::Value &, NoParams &) { return true; } +using ShutdownParams = NoParams; +using ExitParams = NoParams; + +/// Defines how the host (editor) should sync document changes to the language +/// server. +enum class TextDocumentSyncKind { + /// Documents should not be synced at all. + None = 0, + + /// Documents are synced by always sending the full content of the document. + Full = 1, + + /// Documents are synced by sending the full content on open. After that + /// only incremental updates to the document are send. + Incremental = 2, +}; + +struct CompletionItemClientCapabilities { + /// Client supports snippets as insert text. + bool snippetSupport = false; + /// Client supports commit characters on a completion item. + bool commitCharacterSupport = false; + // Client supports the follow content formats for the documentation property. + // The order describes the preferred format of the client. + // NOTE: not used by clangd at the moment. + // std::vector documentationFormat; +}; +bool fromJSON(const llvm::json::Value &, CompletionItemClientCapabilities &); + +struct CompletionClientCapabilities { + /// Whether completion supports dynamic registration. + bool dynamicRegistration = false; + /// The client supports the following `CompletionItem` specific capabilities. + CompletionItemClientCapabilities completionItem; + // NOTE: not used by clangd at the moment. + // llvm::Optional completionItemKind; + + /// The client supports to send additional context information for a + /// `textDocument/completion` request. + bool contextSupport = false; +}; +bool fromJSON(const llvm::json::Value &, CompletionClientCapabilities &); + +/// A symbol kind. +enum class SymbolKind { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26 +}; + +constexpr auto SymbolKindMin = static_cast(SymbolKind::File); +constexpr auto SymbolKindMax = static_cast(SymbolKind::TypeParameter); +using SymbolKindBitset = std::bitset; + +bool fromJSON(const llvm::json::Value &, SymbolKind &); + +struct SymbolKindCapabilities { + /// The SymbolKinds that the client supports. If not set, the client only + /// supports <= SymbolKind::Array and will not fall back to a valid default + /// value. + llvm::Optional> valueSet; +}; +bool fromJSON(const llvm::json::Value &, std::vector &); +bool fromJSON(const llvm::json::Value &, SymbolKindCapabilities &); +SymbolKind adjustKindToCapability(SymbolKind Kind, + SymbolKindBitset &supportedSymbolKinds); + +struct WorkspaceSymbolCapabilities { + /// Capabilities SymbolKind. + llvm::Optional symbolKind; +}; +bool fromJSON(const llvm::json::Value &, WorkspaceSymbolCapabilities &); + +// FIXME: most of the capabilities are missing from this struct. Only the ones +// used by clangd are currently there. +struct WorkspaceClientCapabilities { + /// Capabilities specific to `workspace/symbol`. + llvm::Optional symbol; +}; +bool fromJSON(const llvm::json::Value &, WorkspaceClientCapabilities &); + +// FIXME: most of the capabilities are missing from this struct. Only the ones +// used by clangd are currently there. +struct TextDocumentClientCapabilities { + /// Capabilities specific to the `textDocument/completion` + CompletionClientCapabilities completion; +}; +bool fromJSON(const llvm::json::Value &, TextDocumentClientCapabilities &); + +struct ClientCapabilities { + // Workspace specific client capabilities. + llvm::Optional workspace; + + // Text document specific client capabilities. + TextDocumentClientCapabilities textDocument; +}; + +bool fromJSON(const llvm::json::Value &, ClientCapabilities &); + +/// Clangd extension to set clangd-specific "initializationOptions" in the +/// "initialize" request and for the "workspace/didChangeConfiguration" +/// notification since the data received is described as 'any' type in LSP. +struct ClangdConfigurationParamsChange { + llvm::Optional compilationDatabasePath; +}; +bool fromJSON(const llvm::json::Value &, ClangdConfigurationParamsChange &); + +struct ClangdInitializationOptions : public ClangdConfigurationParamsChange {}; + +struct InitializeParams { + /// The process Id of the parent process that started + /// the server. Is null if the process has not been started by another + /// process. If the parent process is not alive then the server should exit + /// (see exit notification) its process. + llvm::Optional processId; + + /// The rootPath of the workspace. Is null + /// if no folder is open. + /// + /// @deprecated in favour of rootUri. + llvm::Optional rootPath; + + /// The rootUri of the workspace. Is null if no + /// folder is open. If both `rootPath` and `rootUri` are set + /// `rootUri` wins. + llvm::Optional rootUri; + + // User provided initialization options. + // initializationOptions?: any; + + /// The capabilities provided by the client (editor or tool) + ClientCapabilities capabilities; + + /// The initial trace setting. If omitted trace is disabled ('off'). + llvm::Optional trace; + + // We use this predefined struct because it is easier to use + // than the protocol specified type of 'any'. + llvm::Optional initializationOptions; +}; +bool fromJSON(const llvm::json::Value &, InitializeParams &); + +struct DidOpenTextDocumentParams { + /// The document that was opened. + TextDocumentItem textDocument; + + /// Extension storing per-file metadata, such as compilation flags. + llvm::Optional metadata; +}; +bool fromJSON(const llvm::json::Value &, DidOpenTextDocumentParams &); + +struct DidCloseTextDocumentParams { + /// The document that was closed. + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const llvm::json::Value &, DidCloseTextDocumentParams &); + +struct TextDocumentContentChangeEvent { + /// The range of the document that changed. + llvm::Optional range; + + /// The length of the range that got replaced. + llvm::Optional rangeLength; + + /// The new text of the range/document. + std::string text; +}; +bool fromJSON(const llvm::json::Value &, TextDocumentContentChangeEvent &); + +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; + + /// Forces diagnostics to be generated, or to not be generated, for this + /// version of the file. If not set, diagnostics are eventually consistent: + /// either they will be provided for this version or some subsequent one. + /// This is a clangd extension. + llvm::Optional wantDiagnostics; +}; +bool fromJSON(const llvm::json::Value &, DidChangeTextDocumentParams &); + +enum class FileChangeType { + /// The file got created. + Created = 1, + /// The file got changed. + Changed = 2, + /// The file got deleted. + Deleted = 3 +}; +bool fromJSON(const llvm::json::Value &E, FileChangeType &Out); + +struct FileEvent { + /// The file's URI. + URIForFile uri; + /// The change type. + FileChangeType type = FileChangeType::Created; +}; +bool fromJSON(const llvm::json::Value &, FileEvent &); + +struct DidChangeWatchedFilesParams { + /// The actual file events. + std::vector changes; +}; +bool fromJSON(const llvm::json::Value &, DidChangeWatchedFilesParams &); + +struct DidChangeConfigurationParams { + // We use this predefined struct because it is easier to use + // than the protocol specified type of 'any'. + ClangdConfigurationParamsChange settings; +}; +bool fromJSON(const llvm::json::Value &, DidChangeConfigurationParams &); + +struct FormattingOptions { + /// Size of a tab in spaces. + int tabSize = 0; + + /// Prefer spaces over tabs. + bool insertSpaces = false; +}; +bool fromJSON(const llvm::json::Value &, FormattingOptions &); +llvm::json::Value toJSON(const FormattingOptions &); + +struct DocumentRangeFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The range to format + Range range; + + /// The format options + FormattingOptions options; +}; +bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &); + +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; +}; +bool fromJSON(const llvm::json::Value &, DocumentOnTypeFormattingParams &); + +struct DocumentFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The format options + FormattingOptions options; +}; +bool fromJSON(const llvm::json::Value &, DocumentFormattingParams &); + +struct DocumentSymbolParams { + // The text document to find symbols in. + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const llvm::json::Value &, DocumentSymbolParams &); + +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 = 0; + + /// The diagnostic's code. Can be omitted. + /// Note: Not currently used by clangd + // std::string code; + + /// A human-readable string describing the source of this + /// diagnostic, e.g. 'typescript' or 'super lint'. + /// Note: Not currently used by clangd + // std::string source; + + /// The diagnostic's message. + std::string message; +}; + +/// A LSP-specific comparator used to find diagnostic in a container like +/// std:map. +/// We only use the required fields of Diagnostic to do the comparsion to avoid +/// any regression issues from LSP clients (e.g. VScode), see +/// https://git.io/vbr29 +struct LSPDiagnosticCompare { + bool operator()(const Diagnostic &LHS, const Diagnostic &RHS) const { + return std::tie(LHS.range, LHS.message) < std::tie(RHS.range, RHS.message); + } +}; +bool fromJSON(const llvm::json::Value &, Diagnostic &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Diagnostic &); + +struct CodeActionContext { + /// An array of diagnostics. + std::vector diagnostics; +}; +bool fromJSON(const llvm::json::Value &, CodeActionContext &); + +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; +}; +bool fromJSON(const llvm::json::Value &, CodeActionParams &); + +struct WorkspaceEdit { + /// Holds changes to existing resources. + llvm::Optional>> changes; + + /// Note: "documentChanges" is not currently used because currently there is + /// no support for versioned edits. +}; +bool fromJSON(const llvm::json::Value &, WorkspaceEdit &); +llvm::json::Value toJSON(const WorkspaceEdit &WE); + +/// Exact commands are not specified in the protocol so we define the +/// ones supported by Clangd here. The protocol specifies the command arguments +/// to be "any[]" but to make this safer and more manageable, each command we +/// handle maps to a certain llvm::Optional of some struct to contain its +/// arguments. Different commands could reuse the same llvm::Optional as +/// arguments but a command that needs different arguments would simply add a +/// new llvm::Optional and not use any other ones. In practice this means only +/// one argument type will be parsed and set. +struct ExecuteCommandParams { + // Command to apply fix-its. Uses WorkspaceEdit as argument. + const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND; + + /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND + std::string command; + + // Arguments + llvm::Optional workspaceEdit; +}; +bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &); + +struct Command : public ExecuteCommandParams { + std::string title; +}; + +llvm::json::Value toJSON(const Command &C); + +/// Represents information about programming constructs like variables, classes, +/// interfaces etc. +struct SymbolInformation { + /// The name of this symbol. + std::string name; + + /// The kind of this symbol. + SymbolKind kind; + + /// The location of this symbol. + Location location; + + /// The name of the symbol containing this symbol. + std::string containerName; +}; +llvm::json::Value toJSON(const SymbolInformation &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolInformation &); + +/// The parameters of a Workspace Symbol Request. +struct WorkspaceSymbolParams { + /// A non-empty query string + std::string query; +}; +bool fromJSON(const llvm::json::Value &, WorkspaceSymbolParams &); + +struct ApplyWorkspaceEditParams { + WorkspaceEdit edit; +}; +llvm::json::Value toJSON(const ApplyWorkspaceEditParams &); + +struct TextDocumentPositionParams { + /// The text document. + TextDocumentIdentifier textDocument; + + /// The position inside the text document. + Position position; +}; +bool fromJSON(const llvm::json::Value &, TextDocumentPositionParams &); + +enum class MarkupKind { + PlainText, + Markdown, +}; + +struct MarkupContent { + MarkupKind kind = MarkupKind::PlainText; + std::string value; +}; +llvm::json::Value toJSON(const MarkupContent &MC); + +struct Hover { + /// The hover's content + MarkupContent contents; + + /// An optional range is a range inside a text document + /// that is used to visualize a hover, e.g. by changing the background color. + llvm::Optional range; +}; +llvm::json::Value toJSON(const Hover &H); + +/// 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: + // + // data?: any - A data entry field that is preserved on a completion item + // between a completion and a completion resolve request. +}; +llvm::json::Value toJSON(const CompletionItem &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CompletionItem &); + +bool operator<(const CompletionItem &, const CompletionItem &); + +/// Represents a collection of completion items to be presented in the editor. +struct CompletionList { + /// The list is not complete. Further typing should result in recomputing the + /// list. + bool isIncomplete = false; + + /// The completion items. + std::vector items; +}; +llvm::json::Value toJSON(const CompletionList &); + +/// A single parameter of a particular signature. +struct ParameterInformation { + + /// The label of this parameter. Mandatory. + std::string label; + + /// The documentation of this parameter. Optional. + std::string documentation; +}; +llvm::json::Value toJSON(const ParameterInformation &); + +/// Represents the signature of something callable. +struct SignatureInformation { + + /// The label of this signature. Mandatory. + std::string label; + + /// The documentation of this signature. Optional. + std::string documentation; + + /// The parameters of this signature. + std::vector parameters; +}; +llvm::json::Value toJSON(const SignatureInformation &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, + const SignatureInformation &); + +/// Represents the signature of a callable. +struct SignatureHelp { + + /// The resulting signatures. + std::vector signatures; + + /// The active signature. + int activeSignature = 0; + + /// The active parameter of the active signature. + int activeParameter = 0; +}; +llvm::json::Value toJSON(const SignatureHelp &); + +struct RenameParams { + /// The document that was opened. + TextDocumentIdentifier textDocument; + + /// The position at which this request was sent. + Position position; + + /// The new name of the symbol. + std::string newName; +}; +bool fromJSON(const llvm::json::Value &, RenameParams &); + +enum class DocumentHighlightKind { Text = 1, Read = 2, Write = 3 }; + +/// A document highlight is a range inside a text document which deserves +/// special attention. Usually a document highlight is visualized by changing +/// the background color of its range. + +struct DocumentHighlight { + /// The range this highlight applies to. + Range range; + + /// The highlight kind, default is DocumentHighlightKind.Text. + DocumentHighlightKind kind = DocumentHighlightKind::Text; + + friend bool operator<(const DocumentHighlight &LHS, + const DocumentHighlight &RHS) { + int LHSKind = static_cast(LHS.kind); + int RHSKind = static_cast(RHS.kind); + return std::tie(LHS.range, LHSKind) < std::tie(RHS.range, RHSKind); + } + + friend bool operator==(const DocumentHighlight &LHS, + const DocumentHighlight &RHS) { + return LHS.kind == RHS.kind && LHS.range == RHS.range; + } +}; +llvm::json::Value toJSON(const DocumentHighlight &DH); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentHighlight &); + +} // namespace clangd +} // namespace clang + +namespace llvm { +template <> struct format_provider { + static void format(const clang::clangd::Position &Pos, raw_ostream &OS, + StringRef Style) { + assert(Style.empty() && "style modifiers for this type are not supported"); + OS << Pos; + } +}; +} // namespace llvm + +#endif diff --git a/clangd/ProtocolHandlers.cpp b/clangd/ProtocolHandlers.cpp new file mode 100644 index 000000000..deb5b9d09 --- /dev/null +++ b/clangd/ProtocolHandlers.cpp @@ -0,0 +1,78 @@ +//===--- 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" +#include "Trace.h" + +using namespace clang; +using namespace clang::clangd; +using namespace llvm; + +namespace { + +// Helper for attaching ProtocolCallbacks methods to a JSONRPCDispatcher. +// Invoke like: Registerer("foo", &ProtocolCallbacks::onFoo) +// onFoo should be: void onFoo(Ctx &C, FooParams &Params) +// FooParams should have a fromJSON function. +struct HandlerRegisterer { + template + void operator()(StringRef Method, void (ProtocolCallbacks::*Handler)(Param)) { + // Capture pointers by value, as the lambda will outlive this object. + auto *Callbacks = this->Callbacks; + Dispatcher.registerHandler(Method, [=](const json::Value &RawParams) { + typename std::remove_reference::type P; + if (fromJSON(RawParams, P)) { + (Callbacks->*Handler)(P); + } else { + elog("Failed to decode {0} request.", Method); + } + }); + } + + JSONRPCDispatcher &Dispatcher; + ProtocolCallbacks *Callbacks; +}; + +} // namespace + +void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, + ProtocolCallbacks &Callbacks) { + HandlerRegisterer Register{Dispatcher, &Callbacks}; + + Register("initialize", &ProtocolCallbacks::onInitialize); + Register("shutdown", &ProtocolCallbacks::onShutdown); + Register("exit", &ProtocolCallbacks::onExit); + Register("textDocument/didOpen", &ProtocolCallbacks::onDocumentDidOpen); + Register("textDocument/didClose", &ProtocolCallbacks::onDocumentDidClose); + Register("textDocument/didChange", &ProtocolCallbacks::onDocumentDidChange); + Register("textDocument/rangeFormatting", + &ProtocolCallbacks::onDocumentRangeFormatting); + Register("textDocument/onTypeFormatting", + &ProtocolCallbacks::onDocumentOnTypeFormatting); + Register("textDocument/formatting", &ProtocolCallbacks::onDocumentFormatting); + Register("textDocument/codeAction", &ProtocolCallbacks::onCodeAction); + Register("textDocument/completion", &ProtocolCallbacks::onCompletion); + Register("textDocument/signatureHelp", &ProtocolCallbacks::onSignatureHelp); + Register("textDocument/definition", &ProtocolCallbacks::onGoToDefinition); + Register("textDocument/switchSourceHeader", + &ProtocolCallbacks::onSwitchSourceHeader); + Register("textDocument/rename", &ProtocolCallbacks::onRename); + Register("textDocument/hover", &ProtocolCallbacks::onHover); + Register("textDocument/documentSymbol", &ProtocolCallbacks::onDocumentSymbol); + Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); + Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); + Register("textDocument/documentHighlight", + &ProtocolCallbacks::onDocumentHighlight); + Register("workspace/didChangeConfiguration", + &ProtocolCallbacks::onChangeConfiguration); + Register("workspace/symbol", &ProtocolCallbacks::onWorkspaceSymbol); +} diff --git a/clangd/ProtocolHandlers.h b/clangd/ProtocolHandlers.h new file mode 100644 index 000000000..63fd99dca --- /dev/null +++ b/clangd/ProtocolHandlers.h @@ -0,0 +1,66 @@ +//===--- 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. +// +//===----------------------------------------------------------------------===// +// +// ProtocolHandlers translates incoming JSON requests from JSONRPCDispatcher +// into method calls on ClangLSPServer. +// +// Currently it parses requests into objects, but the ClangLSPServer is +// responsible for producing JSON responses. We should move that here, too. +// +//===----------------------------------------------------------------------===// + +#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 { + +// The interface implemented by ClangLSPServer to handle incoming requests. +class ProtocolCallbacks { +public: + virtual ~ProtocolCallbacks() = default; + + virtual void onInitialize(InitializeParams &Params) = 0; + virtual void onShutdown(ShutdownParams &Params) = 0; + virtual void onExit(ExitParams &Params) = 0; + virtual void onDocumentDidOpen(DidOpenTextDocumentParams &Params) = 0; + virtual void onDocumentDidChange(DidChangeTextDocumentParams &Params) = 0; + virtual void onDocumentDidClose(DidCloseTextDocumentParams &Params) = 0; + virtual void onDocumentFormatting(DocumentFormattingParams &Params) = 0; + virtual void onDocumentSymbol(DocumentSymbolParams &Params) = 0; + virtual void + onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params) = 0; + virtual void + onDocumentRangeFormatting(DocumentRangeFormattingParams &Params) = 0; + virtual void onCodeAction(CodeActionParams &Params) = 0; + virtual void onCompletion(TextDocumentPositionParams &Params) = 0; + virtual void onSignatureHelp(TextDocumentPositionParams &Params) = 0; + virtual void onGoToDefinition(TextDocumentPositionParams &Params) = 0; + virtual void onSwitchSourceHeader(TextDocumentIdentifier &Params) = 0; + virtual void onFileEvent(DidChangeWatchedFilesParams &Params) = 0; + virtual void onCommand(ExecuteCommandParams &Params) = 0; + virtual void onWorkspaceSymbol(WorkspaceSymbolParams &Params) = 0; + virtual void onRename(RenameParams &Parames) = 0; + virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0; + virtual void onHover(TextDocumentPositionParams &Params) = 0; + virtual void onChangeConfiguration(DidChangeConfigurationParams &Params) = 0; +}; + +void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, + ProtocolCallbacks &Callbacks); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Quality.cpp b/clangd/Quality.cpp new file mode 100644 index 000000000..61195b566 --- /dev/null +++ b/clangd/Quality.cpp @@ -0,0 +1,398 @@ +//===--- Quality.cpp --------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +#include "Quality.h" +#include "FileDistance.h" +#include "URI.h" +#include "index/Index.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/DeclVisitor.h" +#include "clang/Basic/CharInfo.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Sema/CodeCompleteConsumer.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/MathExtras.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace clang { +namespace clangd { +using namespace llvm; +static bool isReserved(StringRef Name) { + // FIXME: Should we exclude _Bool and others recognized by the standard? + return Name.size() >= 2 && Name[0] == '_' && + (isUppercase(Name[1]) || Name[1] == '_'); +} + +static bool hasDeclInMainFile(const Decl &D) { + auto &SourceMgr = D.getASTContext().getSourceManager(); + for (auto *Redecl : D.redecls()) { + auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation()); + if (SourceMgr.isWrittenInMainFile(Loc)) + return true; + } + return false; +} + +static bool hasUsingDeclInMainFile(const CodeCompletionResult &R) { + const auto &Context = R.Declaration->getASTContext(); + const auto &SourceMgr = Context.getSourceManager(); + if (R.ShadowDecl) { + const auto Loc = SourceMgr.getExpansionLoc(R.ShadowDecl->getLocation()); + if (SourceMgr.isWrittenInMainFile(Loc)) + return true; + } + return false; +} + +static SymbolQualitySignals::SymbolCategory categorize(const NamedDecl &ND) { + class Switch + : public ConstDeclVisitor { + public: +#define MAP(DeclType, Category) \ + SymbolQualitySignals::SymbolCategory Visit##DeclType(const DeclType *) { \ + return SymbolQualitySignals::Category; \ + } + MAP(NamespaceDecl, Namespace); + MAP(NamespaceAliasDecl, Namespace); + MAP(TypeDecl, Type); + MAP(TypeAliasTemplateDecl, Type); + MAP(ClassTemplateDecl, Type); + MAP(CXXConstructorDecl, Constructor); + MAP(ValueDecl, Variable); + MAP(VarTemplateDecl, Variable); + MAP(FunctionDecl, Function); + MAP(FunctionTemplateDecl, Function); + MAP(Decl, Unknown); +#undef MAP + }; + return Switch().Visit(&ND); +} + +static SymbolQualitySignals::SymbolCategory +categorize(const CodeCompletionResult &R) { + if (R.Declaration) + return categorize(*R.Declaration); + if (R.Kind == CodeCompletionResult::RK_Macro) + return SymbolQualitySignals::Macro; + // Everything else is a keyword or a pattern. Patterns are mostly keywords + // too, except a few which we recognize by cursor kind. + switch (R.CursorKind) { + case CXCursor_CXXMethod: + return SymbolQualitySignals::Function; + case CXCursor_ModuleImportDecl: + return SymbolQualitySignals::Namespace; + case CXCursor_MacroDefinition: + return SymbolQualitySignals::Macro; + case CXCursor_TypeRef: + return SymbolQualitySignals::Type; + case CXCursor_MemberRef: + return SymbolQualitySignals::Variable; + case CXCursor_Constructor: + return SymbolQualitySignals::Constructor; + default: + return SymbolQualitySignals::Keyword; + } +} + +static SymbolQualitySignals::SymbolCategory +categorize(const index::SymbolInfo &D) { + switch (D.Kind) { + case index::SymbolKind::Namespace: + case index::SymbolKind::NamespaceAlias: + return SymbolQualitySignals::Namespace; + case index::SymbolKind::Macro: + return SymbolQualitySignals::Macro; + case index::SymbolKind::Enum: + case index::SymbolKind::Struct: + case index::SymbolKind::Class: + case index::SymbolKind::Protocol: + case index::SymbolKind::Extension: + case index::SymbolKind::Union: + case index::SymbolKind::TypeAlias: + return SymbolQualitySignals::Type; + case index::SymbolKind::Function: + case index::SymbolKind::ClassMethod: + case index::SymbolKind::InstanceMethod: + case index::SymbolKind::StaticMethod: + case index::SymbolKind::InstanceProperty: + case index::SymbolKind::ClassProperty: + case index::SymbolKind::StaticProperty: + case index::SymbolKind::Destructor: + case index::SymbolKind::ConversionFunction: + return SymbolQualitySignals::Function; + case index::SymbolKind::Constructor: + return SymbolQualitySignals::Constructor; + case index::SymbolKind::Variable: + case index::SymbolKind::Field: + case index::SymbolKind::EnumConstant: + case index::SymbolKind::Parameter: + return SymbolQualitySignals::Variable; + case index::SymbolKind::Using: + case index::SymbolKind::Module: + case index::SymbolKind::Unknown: + return SymbolQualitySignals::Unknown; + } + llvm_unreachable("Unknown index::SymbolKind"); +} + +static bool isInstanceMember(const NamedDecl *ND) { + if (!ND) + return false; + if (const auto *TP = dyn_cast(ND)) + ND = TP->TemplateDecl::getTemplatedDecl(); + if (const auto *CM = dyn_cast(ND)) + return !CM->isStatic(); + return isa(ND); // Note that static fields are VarDecl. +} + +static bool isInstanceMember(const index::SymbolInfo &D) { + switch (D.Kind) { + case index::SymbolKind::InstanceMethod: + case index::SymbolKind::InstanceProperty: + case index::SymbolKind::Field: + return true; + default: + return false; + } +} + +void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) { + if (SemaCCResult.Availability == CXAvailability_Deprecated) + Deprecated = true; + + Category = categorize(SemaCCResult); + + if (SemaCCResult.Declaration) { + if (auto *ID = SemaCCResult.Declaration->getIdentifier()) + ReservedName = ReservedName || isReserved(ID->getName()); + } else if (SemaCCResult.Kind == CodeCompletionResult::RK_Macro) + ReservedName = ReservedName || isReserved(SemaCCResult.Macro->getName()); +} + +void SymbolQualitySignals::merge(const Symbol &IndexResult) { + References = std::max(IndexResult.References, References); + Category = categorize(IndexResult.SymInfo); + ReservedName = ReservedName || isReserved(IndexResult.Name); +} + +float SymbolQualitySignals::evaluate() const { + float Score = 1; + + // This avoids a sharp gradient for tail symbols, and also neatly avoids the + // question of whether 0 references means a bad symbol or missing data. + if (References >= 10) { + // Use a sigmoid style boosting function, which flats out nicely for large + // numbers (e.g. 2.58 for 1M refererences). + // The following boosting function is equivalent to: + // m = 0.06 + // f = 12.0 + // boost = f * sigmoid(m * std::log(References)) - 0.5 * f + 0.59 + // Sample data points: (10, 1.00), (100, 1.41), (1000, 1.82), + // (10K, 2.21), (100K, 2.58), (1M, 2.94) + float S = std::pow(References, -0.06); + Score *= 6.0 * (1 - S) / (1 + S) + 0.59; + } + + if (Deprecated) + Score *= 0.1f; + if (ReservedName) + Score *= 0.1f; + + switch (Category) { + case Keyword: // Often relevant, but misses most signals. + Score *= 4; // FIXME: important keywords should have specific boosts. + break; + case Type: + case Function: + case Variable: + Score *= 1.1f; + break; + case Namespace: + Score *= 0.8f; + break; + case Macro: + Score *= 0.2f; + break; + case Unknown: + case Constructor: // No boost constructors so they are after class types. + break; + } + + return Score; +} + +raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) { + OS << formatv("=== Symbol quality: {0}\n", S.evaluate()); + OS << formatv("\tReferences: {0}\n", S.References); + OS << formatv("\tDeprecated: {0}\n", S.Deprecated); + OS << formatv("\tReserved name: {0}\n", S.ReservedName); + OS << formatv("\tCategory: {0}\n", static_cast(S.Category)); + return OS; +} + +static SymbolRelevanceSignals::AccessibleScope +computeScope(const NamedDecl *D) { + // Injected "Foo" within the class "Foo" has file scope, not class scope. + const DeclContext *DC = D->getDeclContext(); + if (auto *R = dyn_cast_or_null(D)) + if (R->isInjectedClassName()) + DC = DC->getParent(); + // Class constructor should have the same scope as the class. + if (isa(D)) + DC = DC->getParent(); + bool InClass = false; + for (; !DC->isFileContext(); DC = DC->getParent()) { + if (DC->isFunctionOrMethod()) + return SymbolRelevanceSignals::FunctionScope; + InClass = InClass || DC->isRecord(); + } + if (InClass) + return SymbolRelevanceSignals::ClassScope; + // This threshold could be tweaked, e.g. to treat module-visible as global. + if (D->getLinkageInternal() < ExternalLinkage) + return SymbolRelevanceSignals::FileScope; + return SymbolRelevanceSignals::GlobalScope; +} + +void SymbolRelevanceSignals::merge(const Symbol &IndexResult) { + // FIXME: Index results always assumed to be at global scope. If Scope becomes + // relevant to non-completion requests, we should recognize class members etc. + + SymbolURI = IndexResult.CanonicalDeclaration.FileURI; + IsInstanceMember |= isInstanceMember(IndexResult.SymInfo); +} + +void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) { + if (SemaCCResult.Availability == CXAvailability_NotAvailable || + SemaCCResult.Availability == CXAvailability_NotAccessible) + Forbidden = true; + + if (SemaCCResult.Declaration) { + // We boost things that have decls in the main file. We give a fixed score + // for all other declarations in sema as they are already included in the + // translation unit. + float DeclProximity = (hasDeclInMainFile(*SemaCCResult.Declaration) || + hasUsingDeclInMainFile(SemaCCResult)) + ? 1.0 + : 0.6; + SemaProximityScore = std::max(DeclProximity, SemaProximityScore); + IsInstanceMember |= isInstanceMember(SemaCCResult.Declaration); + } + + // Declarations are scoped, others (like macros) are assumed global. + if (SemaCCResult.Declaration) + Scope = std::min(Scope, computeScope(SemaCCResult.Declaration)); +} + +static std::pair proximityScore(llvm::StringRef SymbolURI, + URIDistance *D) { + if (!D || SymbolURI.empty()) + return {0.f, 0u}; + unsigned Distance = D->distance(SymbolURI); + // Assume approximately default options are used for sensible scoring. + return {std::exp(Distance * -0.4f / FileDistanceOptions().UpCost), Distance}; +} + +float SymbolRelevanceSignals::evaluate() const { + float Score = 1; + + if (Forbidden) + return 0; + + Score *= NameMatch; + + // Proximity scores are [0,1] and we translate them into a multiplier in the + // range from 1 to 3. + Score *= 1 + 2 * std::max(proximityScore(SymbolURI, FileProximityMatch).first, + SemaProximityScore); + + // Symbols like local variables may only be referenced within their scope. + // Conversely if we're in that scope, it's likely we'll reference them. + if (Query == CodeComplete) { + // The narrower the scope where a symbol is visible, the more likely it is + // to be relevant when it is available. + switch (Scope) { + case GlobalScope: + break; + case FileScope: + Score *= 1.5; + break; + case ClassScope: + Score *= 2; + break; + case FunctionScope: + Score *= 4; + break; + } + } + + // Penalize non-instance members when they are accessed via a class instance. + if (!IsInstanceMember && + (Context == CodeCompletionContext::CCC_DotMemberAccess || + Context == CodeCompletionContext::CCC_ArrowMemberAccess)) { + Score *= 0.5; + } + + return Score; +} + +raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) { + OS << formatv("=== Symbol relevance: {0}\n", S.evaluate()); + OS << formatv("\tName match: {0}\n", S.NameMatch); + OS << formatv("\tForbidden: {0}\n", S.Forbidden); + OS << formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember); + OS << formatv("\tContext: {0}\n", getCompletionKindString(S.Context)); + OS << formatv("\tSymbol URI: {0}\n", S.SymbolURI); + if (S.FileProximityMatch) { + auto Score = proximityScore(S.SymbolURI, S.FileProximityMatch); + OS << formatv("\tIndex proximity: {0} (distance={1})\n", Score.first, + Score.second); + } + OS << formatv("\tSema proximity: {0}\n", S.SemaProximityScore); + OS << formatv("\tQuery type: {0}\n", static_cast(S.Query)); + OS << formatv("\tScope: {0}\n", static_cast(S.Scope)); + return OS; +} + +float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance) { + return SymbolQuality * SymbolRelevance; +} + +// Produces an integer that sorts in the same order as F. +// That is: a < b <==> encodeFloat(a) < encodeFloat(b). +static uint32_t encodeFloat(float F) { + static_assert(std::numeric_limits::is_iec559, ""); + constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1); + + // Get the bits of the float. Endianness is the same as for integers. + uint32_t U = FloatToBits(F); + // IEEE 754 floats compare like sign-magnitude integers. + if (U & TopBit) // Negative float. + return 0 - U; // Map onto the low half of integers, order reversed. + return U + TopBit; // Positive floats map onto the high half of integers. +} + +std::string sortText(float Score, llvm::StringRef Name) { + // We convert -Score to an integer, and hex-encode for readability. + // Example: [0.5, "foo"] -> "41000000foo" + std::string S; + llvm::raw_string_ostream OS(S); + write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower, + /*Width=*/2 * sizeof(Score)); + OS << Name; + OS.flush(); + return S; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Quality.h b/clangd/Quality.h new file mode 100644 index 000000000..0aed496be --- /dev/null +++ b/clangd/Quality.h @@ -0,0 +1,167 @@ +//===--- Quality.h - Ranking alternatives for ambiguous queries -*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +/// +/// Some operations such as code completion produce a set of candidates. +/// Usually the user can choose between them, but we should put the best options +/// at the top (they're easier to select, and more likely to be seen). +/// +/// This file defines building blocks for ranking candidates. +/// It's used by the features directly and also in the implementation of +/// indexes, as indexes also need to heuristically limit their results. +/// +/// The facilities here are: +/// - retrieving scoring signals from e.g. indexes, AST, CodeCompletionString +/// These are structured in a way that they can be debugged, and are fairly +/// consistent regardless of the source. +/// - compute scores from scoring signals. These are suitable for sorting. +/// - sorting utilities like the TopN container. +/// These could be split up further to isolate dependencies if we care. +/// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H +#include "clang/Sema/CodeCompleteConsumer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +namespace llvm { +class raw_ostream; +} +namespace clang { +class CodeCompletionResult; +namespace clangd { +struct Symbol; +class URIDistance; + +// Signals structs are designed to be aggregated from 0 or more sources. +// A default instance has neutral signals, and sources are merged into it. +// They can be dumped for debugging, and evaluate()d into a score. + +/// Attributes of a symbol that affect how much we like it. +struct SymbolQualitySignals { + bool Deprecated = false; + bool ReservedName = false; // __foo, _Foo are usually implementation details. + // FIXME: make these findable once user types _. + unsigned References = 0; + + enum SymbolCategory { + Unknown = 0, + Variable, + Macro, + Type, + Function, + Constructor, + Namespace, + Keyword, + } Category = Unknown; + + void merge(const CodeCompletionResult &SemaCCResult); + void merge(const Symbol &IndexResult); + + // Condense these signals down to a single number, higher is better. + float evaluate() const; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &, + const SymbolQualitySignals &); + +/// Attributes of a symbol-query pair that affect how much we like it. +struct SymbolRelevanceSignals { + /// 0-1+ fuzzy-match score for unqualified name. Must be explicitly assigned. + float NameMatch = 1; + bool Forbidden = false; // Unavailable (e.g const) or inaccessible (private). + + URIDistance *FileProximityMatch = nullptr; + /// This is used to calculate proximity between the index symbol and the + /// query. + llvm::StringRef SymbolURI; + /// Proximity between best declaration and the query. [0-1], 1 is closest. + /// FIXME: unify with index proximity score - signals should be + /// source-independent. + float SemaProximityScore = 0; + + // An approximate measure of where we expect the symbol to be used. + enum AccessibleScope { + FunctionScope, + ClassScope, + FileScope, + GlobalScope, + } Scope = GlobalScope; + + enum QueryType { + CodeComplete, + Generic, + } Query = Generic; + + CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other; + + // Whether symbol is an instance member of a class. + bool IsInstanceMember = false; + + void merge(const CodeCompletionResult &SemaResult); + void merge(const Symbol &IndexResult); + + // Condense these signals down to a single number, higher is better. + float evaluate() const; +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &, + const SymbolRelevanceSignals &); + +/// Combine symbol quality and relevance into a single score. +float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance); + +/// TopN is a lossy container that preserves only the "best" N elements. +template > class TopN { +public: + using value_type = T; + TopN(size_t N, Compare Greater = Compare()) + : N(N), Greater(std::move(Greater)) {} + + // Adds a candidate to the set. + // Returns true if a candidate was dropped to get back under N. + bool push(value_type &&V) { + bool Dropped = false; + if (Heap.size() >= N) { + Dropped = true; + if (N > 0 && Greater(V, Heap.front())) { + std::pop_heap(Heap.begin(), Heap.end(), Greater); + Heap.back() = std::move(V); + std::push_heap(Heap.begin(), Heap.end(), Greater); + } + } else { + Heap.push_back(std::move(V)); + std::push_heap(Heap.begin(), Heap.end(), Greater); + } + assert(Heap.size() <= N); + assert(std::is_heap(Heap.begin(), Heap.end(), Greater)); + return Dropped; + } + + // Returns candidates from best to worst. + std::vector items() && { + std::sort_heap(Heap.begin(), Heap.end(), Greater); + assert(Heap.size() <= N); + return std::move(Heap); + } + +private: + const size_t N; + std::vector Heap; // Min-heap, comparator is Greater. + Compare Greater; +}; + +/// Returns a string that sorts in the same order as (-Score, Tiebreak), for +/// LSP. (The highest score compares smallest so it sorts at the top). +std::string sortText(float Score, llvm::StringRef Tiebreak = ""); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/SourceCode.cpp b/clangd/SourceCode.cpp new file mode 100644 index 000000000..d302518b0 --- /dev/null +++ b/clangd/SourceCode.cpp @@ -0,0 +1,203 @@ +//===--- SourceCode.h - Manipulating source code as strings -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "SourceCode.h" + +#include "Logger.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +// Here be dragons. LSP positions use columns measured in *UTF-16 code units*! +// Clangd uses UTF-8 and byte-offsets internally, so conversion is nontrivial. + +// Iterates over unicode codepoints in the (UTF-8) string. For each, +// invokes CB(UTF-8 length, UTF-16 length), and breaks if it returns true. +// Returns true if CB returned true, false if we hit the end of string. +template +static bool iterateCodepoints(StringRef U8, const Callback &CB) { + for (size_t I = 0; I < U8.size();) { + unsigned char C = static_cast(U8[I]); + if (LLVM_LIKELY(!(C & 0x80))) { // ASCII character. + if (CB(1, 1)) + return true; + ++I; + continue; + } + // This convenient property of UTF-8 holds for all non-ASCII characters. + size_t UTF8Length = countLeadingOnes(C); + // 0xxx is ASCII, handled above. 10xxx is a trailing byte, invalid here. + // 11111xxx is not valid UTF-8 at all. Assert because it's probably our bug. + assert((UTF8Length >= 2 && UTF8Length <= 4) && + "Invalid UTF-8, or transcoding bug?"); + I += UTF8Length; // Skip over all trailing bytes. + // A codepoint takes two UTF-16 code unit if it's astral (outside BMP). + // Astral codepoints are encoded as 4 bytes in UTF-8 (11110xxx ...) + if (CB(UTF8Length, UTF8Length == 4 ? 2 : 1)) + return true; + } + return false; +} + +// Returns the offset into the string that matches \p Units UTF-16 code units. +// Conceptually, this converts to UTF-16, truncates to CodeUnits, converts back +// to UTF-8, and returns the length in bytes. +static size_t measureUTF16(StringRef U8, int U16Units, bool &Valid) { + size_t Result = 0; + Valid = U16Units == 0 || iterateCodepoints(U8, [&](int U8Len, int U16Len) { + Result += U8Len; + U16Units -= U16Len; + return U16Units <= 0; + }); + if (U16Units < 0) // Offset was into the middle of a surrogate pair. + Valid = false; + // Don't return an out-of-range index if we overran. + return std::min(Result, U8.size()); +} + +// Counts the number of UTF-16 code units needed to represent a string. +// Like most strings in clangd, the input is UTF-8 encoded. +static size_t utf16Len(StringRef U8) { + // A codepoint takes two UTF-16 code unit if it's astral (outside BMP). + // Astral codepoints are encoded as 4 bytes in UTF-8, starting with 11110xxx. + size_t Count = 0; + iterateCodepoints(U8, [&](int U8Len, int U16Len) { + Count += U16Len; + return false; + }); + return Count; +} + +llvm::Expected positionToOffset(StringRef Code, Position P, + bool AllowColumnsBeyondLineLength) { + if (P.line < 0) + return llvm::make_error( + llvm::formatv("Line value can't be negative ({0})", P.line), + llvm::errc::invalid_argument); + if (P.character < 0) + return llvm::make_error( + llvm::formatv("Character value can't be negative ({0})", P.character), + llvm::errc::invalid_argument); + size_t StartOfLine = 0; + for (int I = 0; I != P.line; ++I) { + size_t NextNL = Code.find('\n', StartOfLine); + if (NextNL == StringRef::npos) + return llvm::make_error( + llvm::formatv("Line value is out of range ({0})", P.line), + llvm::errc::invalid_argument); + StartOfLine = NextNL + 1; + } + + size_t NextNL = Code.find('\n', StartOfLine); + if (NextNL == StringRef::npos) + NextNL = Code.size(); + + bool Valid; + size_t ByteOffsetInLine = measureUTF16( + Code.substr(StartOfLine, NextNL - StartOfLine), P.character, Valid); + if (!Valid && !AllowColumnsBeyondLineLength) + return llvm::make_error( + llvm::formatv("UTF-16 offset {0} is invalid for line {1}", P.character, + P.line), + llvm::errc::invalid_argument); + return StartOfLine + ByteOffsetInLine; +} + +Position offsetToPosition(StringRef Code, size_t Offset) { + Offset = std::min(Code.size(), Offset); + StringRef Before = Code.substr(0, Offset); + int Lines = Before.count('\n'); + size_t PrevNL = Before.rfind('\n'); + size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1); + Position Pos; + Pos.line = Lines; + Pos.character = utf16Len(Before.substr(StartOfLine)); + return Pos; +} + +Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc) { + // We use the SourceManager's line tables, but its column number is in bytes. + FileID FID; + unsigned Offset; + std::tie(FID, Offset) = SM.getDecomposedSpellingLoc(Loc); + Position P; + P.line = static_cast(SM.getLineNumber(FID, Offset)) - 1; + bool Invalid = false; + StringRef Code = SM.getBufferData(FID, &Invalid); + if (!Invalid) { + auto ColumnInBytes = SM.getColumnNumber(FID, Offset) - 1; + auto LineSoFar = Code.substr(Offset - ColumnInBytes, ColumnInBytes); + P.character = utf16Len(LineSoFar); + } + return P; +} + +Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) { + // Clang is 1-based, LSP uses 0-based indexes. + Position Begin = sourceLocToPosition(SM, R.getBegin()); + Position End = sourceLocToPosition(SM, R.getEnd()); + + return {Begin, End}; +} + +std::pair offsetToClangLineColumn(StringRef Code, + size_t Offset) { + Offset = std::min(Code.size(), Offset); + StringRef Before = Code.substr(0, Offset); + int Lines = Before.count('\n'); + size_t PrevNL = Before.rfind('\n'); + size_t StartOfLine = (PrevNL == StringRef::npos) ? 0 : (PrevNL + 1); + return {Lines + 1, Offset - StartOfLine + 1}; +} + +std::pair +splitQualifiedName(llvm::StringRef QName) { + size_t Pos = QName.rfind("::"); + if (Pos == llvm::StringRef::npos) + return {StringRef(), QName}; + return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; +} + +TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) { + Range ReplacementRange = { + offsetToPosition(Code, R.getOffset()), + offsetToPosition(Code, R.getOffset() + R.getLength())}; + return {ReplacementRange, R.getReplacementText()}; +} + +std::vector replacementsToEdits(StringRef Code, + const tooling::Replacements &Repls) { + std::vector Edits; + for (const auto &R : Repls) + Edits.push_back(replacementToEdit(Code, R)); + return Edits; +} + +llvm::Optional +getAbsoluteFilePath(const FileEntry *F, const SourceManager &SourceMgr) { + SmallString<64> FilePath = F->tryGetRealPathName(); + if (FilePath.empty()) + FilePath = F->getName(); + if (!llvm::sys::path::is_absolute(FilePath)) { + if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) { + log("Could not turn relative path to absolute: {0}", FilePath); + return llvm::None; + } + } + return FilePath.str().str(); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/SourceCode.h b/clangd/SourceCode.h new file mode 100644 index 000000000..b67fe6ecf --- /dev/null +++ b/clangd/SourceCode.h @@ -0,0 +1,69 @@ +//===--- SourceCode.h - Manipulating source code as strings -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Various code that examines C++ source code without using heavy AST machinery +// (and often not even the lexer). To be used sparingly! +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H +#include "Protocol.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Core/Replacement.h" + +namespace clang { +class SourceManager; + +namespace clangd { + +/// Turn a [line, column] pair into an offset in Code. +/// +/// If P.character exceeds the line length, returns the offset at end-of-line. +/// (If !AllowColumnsBeyondLineLength, then returns an error instead). +/// If the line number is out of range, returns an error. +/// +/// The returned value is in the range [0, Code.size()]. +llvm::Expected +positionToOffset(llvm::StringRef Code, Position P, + bool AllowColumnsBeyondLineLength = true); + +/// Turn an offset in Code into a [line, column] pair. +/// The offset must be in range [0, Code.size()]. +Position offsetToPosition(llvm::StringRef Code, size_t Offset); + +/// Turn a SourceLocation into a [line, column] pair. +/// FIXME: This should return an error if the location is invalid. +Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc); + +// Converts a half-open clang source range to an LSP range. +// Note that clang also uses closed source ranges, which this can't handle! +Range halfOpenToRange(const SourceManager &SM, CharSourceRange R); + +// Converts an offset to a clang line/column (1-based, columns are bytes). +// The offset must be in range [0, Code.size()]. +// Prefer to use SourceManager if one is available. +std::pair offsetToClangLineColumn(llvm::StringRef Code, + size_t Offset); + +/// From "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no +/// qualifier. +std::pair +splitQualifiedName(llvm::StringRef QName); + +TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R); + +std::vector replacementsToEdits(StringRef Code, + const tooling::Replacements &Repls); + +/// Get the absolute file path of a given file entry. +llvm::Optional getAbsoluteFilePath(const FileEntry *F, + const SourceManager &SourceMgr); +} // namespace clangd +} // namespace clang +#endif diff --git a/clangd/TUScheduler.cpp b/clangd/TUScheduler.cpp new file mode 100644 index 000000000..81a75bbc2 --- /dev/null +++ b/clangd/TUScheduler.cpp @@ -0,0 +1,764 @@ +//===--- TUScheduler.cpp -----------------------------------------*-C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// For each file, managed by TUScheduler, we create a single ASTWorker that +// manages an AST for that file. All operations that modify or read the AST are +// run on a separate dedicated thread asynchronously in FIFO order. +// +// We start processing each update immediately after we receive it. If two or +// more updates come subsequently without reads in-between, we attempt to drop +// an older one to not waste time building the ASTs we don't need. +// +// The processing thread of the ASTWorker is also responsible for building the +// preamble. However, unlike AST, the same preamble can be read concurrently, so +// we run each of async preamble reads on its own thread. +// +// To limit the concurrent load that clangd produces we mantain a semaphore that +// keeps more than a fixed number of threads from running concurrently. +// +// Rationale for cancelling updates. +// LSP clients can send updates to clangd on each keystroke. Some files take +// significant time to parse (e.g. a few seconds) and clangd can get starved by +// the updates to those files. Therefore we try to process only the last update, +// if possible. +// Our current strategy to do that is the following: +// - For each update we immediately schedule rebuild of the AST. +// - Rebuild of the AST checks if it was cancelled before doing any actual work. +// If it was, it does not do an actual rebuild, only reports llvm::None to the +// callback +// - When adding an update, we cancel the last update in the queue if it didn't +// have any reads. +// There is probably a optimal ways to do that. One approach we might take is +// the following: +// - For each update we remember the pending inputs, but delay rebuild of the +// AST for some timeout. +// - If subsequent updates come before rebuild was started, we replace the +// pending inputs and reset the timer. +// - If any reads of the AST are scheduled, we start building the AST +// immediately. + +#include "TUScheduler.h" +#include "Logger.h" +#include "Trace.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/PCHContainerOperations.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Path.h" +#include +#include +#include +#include + +namespace clang { +namespace clangd { +using std::chrono::steady_clock; + +namespace { +class ASTWorker; +} + +/// An LRU cache of idle ASTs. +/// Because we want to limit the overall number of these we retain, the cache +/// owns ASTs (and may evict them) while their workers are idle. +/// Workers borrow ASTs when active, and return them when done. +class TUScheduler::ASTCache { +public: + using Key = const ASTWorker *; + + ASTCache(unsigned MaxRetainedASTs) : MaxRetainedASTs(MaxRetainedASTs) {} + + /// Returns result of getUsedBytes() for the AST cached by \p K. + /// If no AST is cached, 0 is returned. + std::size_t getUsedBytes(Key K) { + std::lock_guard Lock(Mut); + auto It = findByKey(K); + if (It == LRU.end() || !It->second) + return 0; + return It->second->getUsedBytes(); + } + + /// Store the value in the pool, possibly removing the last used AST. + /// The value should not be in the pool when this function is called. + void put(Key K, std::unique_ptr V) { + std::unique_lock Lock(Mut); + assert(findByKey(K) == LRU.end()); + + LRU.insert(LRU.begin(), {K, std::move(V)}); + if (LRU.size() <= MaxRetainedASTs) + return; + // We're past the limit, remove the last element. + std::unique_ptr ForCleanup = std::move(LRU.back().second); + LRU.pop_back(); + // Run the expensive destructor outside the lock. + Lock.unlock(); + ForCleanup.reset(); + } + + /// Returns the cached value for \p K, or llvm::None if the value is not in + /// the cache anymore. If nullptr was cached for \p K, this function will + /// return a null unique_ptr wrapped into an optional. + llvm::Optional> take(Key K) { + std::unique_lock Lock(Mut); + auto Existing = findByKey(K); + if (Existing == LRU.end()) + return llvm::None; + std::unique_ptr V = std::move(Existing->second); + LRU.erase(Existing); + // GCC 4.8 fails to compile `return V;`, as it tries to call the copy + // constructor of unique_ptr, so we call the move ctor explicitly to avoid + // this miscompile. + return llvm::Optional>(std::move(V)); + } + +private: + using KVPair = std::pair>; + + std::vector::iterator findByKey(Key K) { + return std::find_if(LRU.begin(), LRU.end(), + [K](const KVPair &P) { return P.first == K; }); + } + + std::mutex Mut; + unsigned MaxRetainedASTs; + /// Items sorted in LRU order, i.e. first item is the most recently accessed + /// one. + std::vector LRU; /* GUARDED_BY(Mut) */ +}; + +namespace { +class ASTWorkerHandle; + +/// Owns one instance of the AST, schedules updates and reads of it. +/// Also responsible for building and providing access to the preamble. +/// Each ASTWorker processes the async requests sent to it on a separate +/// dedicated thread. +/// The ASTWorker that manages the AST is shared by both the processing thread +/// and the TUScheduler. The TUScheduler should discard an ASTWorker when +/// remove() is called, but its thread may be busy and we don't want to block. +/// So the workers are accessed via an ASTWorkerHandle. Destroying the handle +/// signals the worker to exit its run loop and gives up shared ownership of the +/// worker. +class ASTWorker { + friend class ASTWorkerHandle; + ASTWorker(PathRef FileName, TUScheduler::ASTCache &LRUCache, + Semaphore &Barrier, bool RunSync, + steady_clock::duration UpdateDebounce, + std::shared_ptr PCHs, + bool StorePreamblesInMemory, + PreambleParsedCallback PreambleCallback); + +public: + /// Create a new ASTWorker and return a handle to it. + /// The processing thread is spawned using \p Tasks. However, when \p Tasks + /// is null, all requests will be processed on the calling thread + /// synchronously instead. \p Barrier is acquired when processing each + /// request, it is be used to limit the number of actively running threads. + static ASTWorkerHandle create(PathRef FileName, + TUScheduler::ASTCache &IdleASTs, + AsyncTaskRunner *Tasks, Semaphore &Barrier, + steady_clock::duration UpdateDebounce, + std::shared_ptr PCHs, + bool StorePreamblesInMemory, + PreambleParsedCallback PreambleCallback); + ~ASTWorker(); + + void update(ParseInputs Inputs, WantDiagnostics, + llvm::unique_function)> OnUpdated); + void + runWithAST(llvm::StringRef Name, + llvm::unique_function)> Action); + bool blockUntilIdle(Deadline Timeout) const; + + std::shared_ptr getPossiblyStalePreamble() const; + /// Wait for the first build of preamble to finish. Preamble itself can be + /// accessed via getPossibleStalePreamble(). Note that this function will + /// return after an unsuccessful build of the preamble too, i.e. result of + /// getPossiblyStalePreamble() can be null even after this function returns. + void waitForFirstPreamble() const; + + std::size_t getUsedBytes() const; + bool isASTCached() const; + +private: + // Must be called exactly once on processing thread. Will return after + // stop() is called on a separate thread and all pending requests are + // processed. + void run(); + /// Signal that run() should finish processing pending requests and exit. + void stop(); + /// Adds a new task to the end of the request queue. + void startTask(llvm::StringRef Name, llvm::unique_function Task, + llvm::Optional UpdateType); + /// Determines the next action to perform. + /// All actions that should never run are disarded. + /// Returns a deadline for the next action. If it's expired, run now. + /// scheduleLocked() is called again at the deadline, or if requests arrive. + Deadline scheduleLocked(); + /// Should the first task in the queue be skipped instead of run? + bool shouldSkipHeadLocked() const; + + struct Request { + llvm::unique_function Action; + std::string Name; + steady_clock::time_point AddTime; + Context Ctx; + llvm::Optional UpdateType; + }; + + /// Handles retention of ASTs. + TUScheduler::ASTCache &IdleASTs; + const bool RunSync; + /// Time to wait after an update to see whether another update obsoletes it. + const steady_clock::duration UpdateDebounce; + /// File that ASTWorker is reponsible for. + const Path FileName; + /// Whether to keep the built preambles in memory or on disk. + const bool StorePreambleInMemory; + /// Callback, passed to the preamble builder. + const PreambleParsedCallback PreambleCallback; + /// Helper class required to build the ASTs. + const std::shared_ptr PCHs; + + Semaphore &Barrier; + /// Inputs, corresponding to the current state of AST. + ParseInputs FileInputs; + /// Whether the diagnostics for the current FileInputs were reported to the + /// users before. + bool DiagsWereReported = false; + /// Size of the last AST + /// Guards members used by both TUScheduler and the worker thread. + mutable std::mutex Mutex; + std::shared_ptr LastBuiltPreamble; /* GUARDED_BY(Mutex) */ + /// Becomes ready when the first preamble build finishes. + Notification PreambleWasBuilt; + /// Set to true to signal run() to finish processing. + bool Done; /* GUARDED_BY(Mutex) */ + std::deque Requests; /* GUARDED_BY(Mutex) */ + mutable std::condition_variable RequestsCV; +}; + +/// A smart-pointer-like class that points to an active ASTWorker. +/// In destructor, signals to the underlying ASTWorker that no new requests will +/// be sent and the processing loop may exit (after running all pending +/// requests). +class ASTWorkerHandle { + friend class ASTWorker; + ASTWorkerHandle(std::shared_ptr Worker) + : Worker(std::move(Worker)) { + assert(this->Worker); + } + +public: + ASTWorkerHandle(const ASTWorkerHandle &) = delete; + ASTWorkerHandle &operator=(const ASTWorkerHandle &) = delete; + ASTWorkerHandle(ASTWorkerHandle &&) = default; + ASTWorkerHandle &operator=(ASTWorkerHandle &&) = default; + + ~ASTWorkerHandle() { + if (Worker) + Worker->stop(); + } + + ASTWorker &operator*() { + assert(Worker && "Handle was moved from"); + return *Worker; + } + + ASTWorker *operator->() { + assert(Worker && "Handle was moved from"); + return Worker.get(); + } + + /// Returns an owning reference to the underlying ASTWorker that can outlive + /// the ASTWorkerHandle. However, no new requests to an active ASTWorker can + /// be schedule via the returned reference, i.e. only reads of the preamble + /// are possible. + std::shared_ptr lock() { return Worker; } + +private: + std::shared_ptr Worker; +}; + +ASTWorkerHandle ASTWorker::create(PathRef FileName, + TUScheduler::ASTCache &IdleASTs, + AsyncTaskRunner *Tasks, Semaphore &Barrier, + steady_clock::duration UpdateDebounce, + std::shared_ptr PCHs, + bool StorePreamblesInMemory, + PreambleParsedCallback PreambleCallback) { + std::shared_ptr Worker(new ASTWorker( + FileName, IdleASTs, Barrier, /*RunSync=*/!Tasks, UpdateDebounce, + std::move(PCHs), StorePreamblesInMemory, std::move(PreambleCallback))); + if (Tasks) + Tasks->runAsync("worker:" + llvm::sys::path::filename(FileName), + [Worker]() { Worker->run(); }); + + return ASTWorkerHandle(std::move(Worker)); +} + +ASTWorker::ASTWorker(PathRef FileName, TUScheduler::ASTCache &LRUCache, + Semaphore &Barrier, bool RunSync, + steady_clock::duration UpdateDebounce, + std::shared_ptr PCHs, + bool StorePreamblesInMemory, + PreambleParsedCallback PreambleCallback) + : IdleASTs(LRUCache), RunSync(RunSync), UpdateDebounce(UpdateDebounce), + FileName(FileName), StorePreambleInMemory(StorePreamblesInMemory), + PreambleCallback(std::move(PreambleCallback)), PCHs(std::move(PCHs)), + Barrier(Barrier), Done(false) {} + +ASTWorker::~ASTWorker() { + // Make sure we remove the cached AST, if any. + IdleASTs.take(this); +#ifndef NDEBUG + std::lock_guard Lock(Mutex); + assert(Done && "handle was not destroyed"); + assert(Requests.empty() && "unprocessed requests when destroying ASTWorker"); +#endif +} + +void ASTWorker::update( + ParseInputs Inputs, WantDiagnostics WantDiags, + llvm::unique_function)> OnUpdated) { + auto Task = [=](decltype(OnUpdated) OnUpdated) mutable { + // Will be used to check if we can avoid rebuilding the AST. + bool InputsAreTheSame = + std::tie(FileInputs.CompileCommand, FileInputs.Contents) == + std::tie(Inputs.CompileCommand, Inputs.Contents); + + tooling::CompileCommand OldCommand = std::move(FileInputs.CompileCommand); + bool PrevDiagsWereReported = DiagsWereReported; + FileInputs = Inputs; + DiagsWereReported = false; + + log("Updating file {0} with command [{1}] {2}", FileName, + Inputs.CompileCommand.Directory, + llvm::join(Inputs.CompileCommand.CommandLine, " ")); + // Rebuild the preamble and the AST. + std::unique_ptr Invocation = + buildCompilerInvocation(Inputs); + if (!Invocation) { + elog("Could not build CompilerInvocation for file {0}", FileName); + // Remove the old AST if it's still in cache. + IdleASTs.take(this); + // Make sure anyone waiting for the preamble gets notified it could not + // be built. + PreambleWasBuilt.notify(); + return; + } + + std::shared_ptr OldPreamble = + getPossiblyStalePreamble(); + std::shared_ptr NewPreamble = + buildPreamble(FileName, *Invocation, OldPreamble, OldCommand, Inputs, + PCHs, StorePreambleInMemory, PreambleCallback); + + bool CanReuseAST = InputsAreTheSame && (OldPreamble == NewPreamble); + { + std::lock_guard Lock(Mutex); + if (NewPreamble) + LastBuiltPreamble = NewPreamble; + } + // Before doing the expensive AST reparse, we want to release our reference + // to the old preamble, so it can be freed if there are no other references + // to it. + OldPreamble.reset(); + PreambleWasBuilt.notify(); + + if (!CanReuseAST) { + IdleASTs.take(this); // Remove the old AST if it's still in cache. + } else { + // Since we don't need to rebuild the AST, we might've already reported + // the diagnostics for it. + if (PrevDiagsWereReported) { + DiagsWereReported = true; + // Take a shortcut and don't report the diagnostics, since they should + // not changed. All the clients should handle the lack of OnUpdated() + // call anyway to handle empty result from buildAST. + // FIXME(ibiryukov): the AST could actually change if non-preamble + // includes changed, but we choose to ignore it. + // FIXME(ibiryukov): should we refresh the cache in IdleASTs for the + // current file at this point? + log("Skipping rebuild of the AST for {0}, inputs are the same.", + FileName); + return; + } + } + + // We only need to build the AST if diagnostics were requested. + if (WantDiags == WantDiagnostics::No) + return; + + // Get the AST for diagnostics. + llvm::Optional> AST = IdleASTs.take(this); + if (!AST) { + llvm::Optional NewAST = + buildAST(FileName, std::move(Invocation), Inputs, NewPreamble, PCHs); + AST = NewAST ? llvm::make_unique(std::move(*NewAST)) : nullptr; + } + // We want to report the diagnostics even if this update was cancelled. + // It seems more useful than making the clients wait indefinitely if they + // spam us with updates. + // Note *AST can be still be null if buildAST fails. + if (*AST) { + OnUpdated((*AST)->getDiagnostics()); + DiagsWereReported = true; + } + // Stash the AST in the cache for further use. + IdleASTs.put(this, std::move(*AST)); + }; + + startTask("Update", Bind(Task, std::move(OnUpdated)), WantDiags); +} + +void ASTWorker::runWithAST( + llvm::StringRef Name, + llvm::unique_function)> Action) { + auto Task = [=](decltype(Action) Action) { + llvm::Optional> AST = IdleASTs.take(this); + if (!AST) { + std::unique_ptr Invocation = + buildCompilerInvocation(FileInputs); + // Try rebuilding the AST. + llvm::Optional NewAST = + Invocation + ? buildAST(FileName, + llvm::make_unique(*Invocation), + FileInputs, getPossiblyStalePreamble(), PCHs) + : llvm::None; + AST = NewAST ? llvm::make_unique(std::move(*NewAST)) : nullptr; + } + // Make sure we put the AST back into the LRU cache. + auto _ = llvm::make_scope_exit( + [&AST, this]() { IdleASTs.put(this, std::move(*AST)); }); + // Run the user-provided action. + if (!*AST) + return Action(llvm::make_error( + "invalid AST", llvm::errc::invalid_argument)); + Action(InputsAndAST{FileInputs, **AST}); + }; + startTask(Name, Bind(Task, std::move(Action)), + /*UpdateType=*/llvm::None); +} + +std::shared_ptr +ASTWorker::getPossiblyStalePreamble() const { + std::lock_guard Lock(Mutex); + return LastBuiltPreamble; +} + +void ASTWorker::waitForFirstPreamble() const { + PreambleWasBuilt.wait(); +} + +std::size_t ASTWorker::getUsedBytes() const { + // Note that we don't report the size of ASTs currently used for processing + // the in-flight requests. We used this information for debugging purposes + // only, so this should be fine. + std::size_t Result = IdleASTs.getUsedBytes(this); + if (auto Preamble = getPossiblyStalePreamble()) + Result += Preamble->Preamble.getSize(); + return Result; +} + +bool ASTWorker::isASTCached() const { return IdleASTs.getUsedBytes(this) != 0; } + +void ASTWorker::stop() { + { + std::lock_guard Lock(Mutex); + assert(!Done && "stop() called twice"); + Done = true; + } + RequestsCV.notify_all(); +} + +void ASTWorker::startTask(llvm::StringRef Name, + llvm::unique_function Task, + llvm::Optional UpdateType) { + if (RunSync) { + assert(!Done && "running a task after stop()"); + trace::Span Tracer(Name + ":" + llvm::sys::path::filename(FileName)); + Task(); + return; + } + + { + std::lock_guard Lock(Mutex); + assert(!Done && "running a task after stop()"); + Requests.push_back({std::move(Task), Name, steady_clock::now(), + Context::current().clone(), UpdateType}); + } + RequestsCV.notify_all(); +} + +void ASTWorker::run() { + while (true) { + Request Req; + { + std::unique_lock Lock(Mutex); + for (auto Wait = scheduleLocked(); !Wait.expired(); + Wait = scheduleLocked()) { + if (Done) { + if (Requests.empty()) + return; + else // Even though Done is set, finish pending requests. + break; // However, skip delays to shutdown fast. + } + + // Tracing: we have a next request, attribute this sleep to it. + Optional Ctx; + Optional Tracer; + if (!Requests.empty()) { + Ctx.emplace(Requests.front().Ctx.clone()); + Tracer.emplace("Debounce"); + SPAN_ATTACH(*Tracer, "next_request", Requests.front().Name); + if (!(Wait == Deadline::infinity())) + SPAN_ATTACH(*Tracer, "sleep_ms", + std::chrono::duration_cast( + Wait.time() - steady_clock::now()) + .count()); + } + + wait(Lock, RequestsCV, Wait); + } + Req = std::move(Requests.front()); + // Leave it on the queue for now, so waiters don't see an empty queue. + } // unlock Mutex + + { + std::lock_guard BarrierLock(Barrier); + WithContext Guard(std::move(Req.Ctx)); + trace::Span Tracer(Req.Name); + Req.Action(); + } + + { + std::lock_guard Lock(Mutex); + Requests.pop_front(); + } + RequestsCV.notify_all(); + } +} + +Deadline ASTWorker::scheduleLocked() { + if (Requests.empty()) + return Deadline::infinity(); // Wait for new requests. + while (shouldSkipHeadLocked()) + Requests.pop_front(); + assert(!Requests.empty() && "skipped the whole queue"); + // Some updates aren't dead yet, but never end up being used. + // e.g. the first keystroke is live until obsoleted by the second. + // We debounce "maybe-unused" writes, sleeping 500ms in case they become dead. + // But don't delay reads (including updates where diagnostics are needed). + for (const auto &R : Requests) + if (R.UpdateType == None || R.UpdateType == WantDiagnostics::Yes) + return Deadline::zero(); + // Front request needs to be debounced, so determine when we're ready. + Deadline D(Requests.front().AddTime + UpdateDebounce); + return D; +} + +// Returns true if Requests.front() is a dead update that can be skipped. +bool ASTWorker::shouldSkipHeadLocked() const { + assert(!Requests.empty()); + auto Next = Requests.begin(); + auto UpdateType = Next->UpdateType; + if (!UpdateType) // Only skip updates. + return false; + ++Next; + // An update is live if its AST might still be read. + // That is, if it's not immediately followed by another update. + if (Next == Requests.end() || !Next->UpdateType) + return false; + // The other way an update can be live is if its diagnostics might be used. + switch (*UpdateType) { + case WantDiagnostics::Yes: + return false; // Always used. + case WantDiagnostics::No: + return true; // Always dead. + case WantDiagnostics::Auto: + // Used unless followed by an update that generates diagnostics. + for (; Next != Requests.end(); ++Next) + if (Next->UpdateType == WantDiagnostics::Yes || + Next->UpdateType == WantDiagnostics::Auto) + return true; // Prefer later diagnostics. + return false; + } + llvm_unreachable("Unknown WantDiagnostics"); +} + +bool ASTWorker::blockUntilIdle(Deadline Timeout) const { + std::unique_lock Lock(Mutex); + return wait(Lock, RequestsCV, Timeout, [&] { return Requests.empty(); }); +} + +} // namespace + +unsigned getDefaultAsyncThreadsCount() { + unsigned HardwareConcurrency = std::thread::hardware_concurrency(); + // C++ standard says that hardware_concurrency() + // may return 0, fallback to 1 worker thread in + // that case. + if (HardwareConcurrency == 0) + return 1; + return HardwareConcurrency; +} + +struct TUScheduler::FileData { + /// Latest inputs, passed to TUScheduler::update(). + std::string Contents; + tooling::CompileCommand Command; + ASTWorkerHandle Worker; +}; + +TUScheduler::TUScheduler(unsigned AsyncThreadsCount, + bool StorePreamblesInMemory, + PreambleParsedCallback PreambleCallback, + std::chrono::steady_clock::duration UpdateDebounce, + ASTRetentionPolicy RetentionPolicy) + : StorePreamblesInMemory(StorePreamblesInMemory), + PCHOps(std::make_shared()), + PreambleCallback(std::move(PreambleCallback)), Barrier(AsyncThreadsCount), + IdleASTs(llvm::make_unique(RetentionPolicy.MaxRetainedASTs)), + UpdateDebounce(UpdateDebounce) { + if (0 < AsyncThreadsCount) { + PreambleTasks.emplace(); + WorkerThreads.emplace(); + } +} + +TUScheduler::~TUScheduler() { + // Notify all workers that they need to stop. + Files.clear(); + + // Wait for all in-flight tasks to finish. + if (PreambleTasks) + PreambleTasks->wait(); + if (WorkerThreads) + WorkerThreads->wait(); +} + +bool TUScheduler::blockUntilIdle(Deadline D) const { + for (auto &File : Files) + if (!File.getValue()->Worker->blockUntilIdle(D)) + return false; + if (PreambleTasks) + if (!PreambleTasks->wait(D)) + return false; + return true; +} + +void TUScheduler::update( + PathRef File, ParseInputs Inputs, WantDiagnostics WantDiags, + llvm::unique_function)> OnUpdated) { + std::unique_ptr &FD = Files[File]; + if (!FD) { + // Create a new worker to process the AST-related tasks. + ASTWorkerHandle Worker = ASTWorker::create( + File, *IdleASTs, WorkerThreads ? WorkerThreads.getPointer() : nullptr, + Barrier, UpdateDebounce, PCHOps, StorePreamblesInMemory, + PreambleCallback); + FD = std::unique_ptr(new FileData{ + Inputs.Contents, Inputs.CompileCommand, std::move(Worker)}); + } else { + FD->Contents = Inputs.Contents; + FD->Command = Inputs.CompileCommand; + } + FD->Worker->update(std::move(Inputs), WantDiags, std::move(OnUpdated)); +} + +void TUScheduler::remove(PathRef File) { + bool Removed = Files.erase(File); + if (!Removed) + elog("Trying to remove file from TUScheduler that is not tracked: {0}", + File); +} + +void TUScheduler::runWithAST( + llvm::StringRef Name, PathRef File, + llvm::unique_function)> Action) { + auto It = Files.find(File); + if (It == Files.end()) { + Action(llvm::make_error( + "trying to get AST for non-added document", + llvm::errc::invalid_argument)); + return; + } + + It->second->Worker->runWithAST(Name, std::move(Action)); +} + +void TUScheduler::runWithPreamble( + llvm::StringRef Name, PathRef File, + llvm::unique_function)> Action) { + auto It = Files.find(File); + if (It == Files.end()) { + Action(llvm::make_error( + "trying to get preamble for non-added document", + llvm::errc::invalid_argument)); + return; + } + + if (!PreambleTasks) { + trace::Span Tracer(Name); + SPAN_ATTACH(Tracer, "file", File); + std::shared_ptr Preamble = + It->second->Worker->getPossiblyStalePreamble(); + Action(InputsAndPreamble{It->second->Contents, It->second->Command, + Preamble.get()}); + return; + } + + std::shared_ptr Worker = It->second->Worker.lock(); + auto Task = [Worker, this](std::string Name, std::string File, + std::string Contents, + tooling::CompileCommand Command, Context Ctx, + decltype(Action) Action) mutable { + // We don't want to be running preamble actions before the preamble was + // built for the first time. This avoids extra work of processing the + // preamble headers in parallel multiple times. + Worker->waitForFirstPreamble(); + + std::lock_guard BarrierLock(Barrier); + WithContext Guard(std::move(Ctx)); + trace::Span Tracer(Name); + SPAN_ATTACH(Tracer, "file", File); + std::shared_ptr Preamble = + Worker->getPossiblyStalePreamble(); + Action(InputsAndPreamble{Contents, Command, Preamble.get()}); + }; + + PreambleTasks->runAsync("task:" + llvm::sys::path::filename(File), + Bind(Task, std::string(Name), std::string(File), + It->second->Contents, It->second->Command, + Context::current().clone(), std::move(Action))); +} + +std::vector> +TUScheduler::getUsedBytesPerFile() const { + std::vector> Result; + Result.reserve(Files.size()); + for (auto &&PathAndFile : Files) + Result.push_back( + {PathAndFile.first(), PathAndFile.second->Worker->getUsedBytes()}); + return Result; +} + +std::vector TUScheduler::getFilesWithCachedAST() const { + std::vector Result; + for (auto &&PathAndFile : Files) { + if (!PathAndFile.second->Worker->isASTCached()) + continue; + Result.push_back(PathAndFile.first()); + } + return Result; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/TUScheduler.h b/clangd/TUScheduler.h new file mode 100644 index 000000000..af1ff3656 --- /dev/null +++ b/clangd/TUScheduler.h @@ -0,0 +1,141 @@ +//===--- TUScheduler.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_TUSCHEDULER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TUSCHEDULER_H + +#include "ClangdUnit.h" +#include "Function.h" +#include "Threading.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { +namespace clangd { + +/// Returns a number of a default async threads to use for TUScheduler. +/// Returned value is always >= 1 (i.e. will not cause requests to be processed +/// synchronously). +unsigned getDefaultAsyncThreadsCount(); + +struct InputsAndAST { + const ParseInputs &Inputs; + ParsedAST &AST; +}; + +struct InputsAndPreamble { + llvm::StringRef Contents; + const tooling::CompileCommand &Command; + const PreambleData *Preamble; +}; + +/// Determines whether diagnostics should be generated for a file snapshot. +enum class WantDiagnostics { + Yes, /// Diagnostics must be generated for this snapshot. + No, /// Diagnostics must not be generated for this snapshot. + Auto, /// Diagnostics must be generated for this snapshot or a subsequent one, + /// within a bounded amount of time. +}; + +/// Configuration of the AST retention policy. This only covers retention of +/// *idle* ASTs. If queue has operations requiring the AST, they might be +/// kept in memory. +struct ASTRetentionPolicy { + /// Maximum number of ASTs to be retained in memory when there are no pending + /// requests for them. + unsigned MaxRetainedASTs = 3; +}; + +/// Handles running tasks for ClangdServer and managing the resources (e.g., +/// preambles and ASTs) for opened files. +/// TUScheduler is not thread-safe, only one thread should be providing updates +/// and scheduling tasks. +/// Callbacks are run on a threadpool and it's appropriate to do slow work in +/// them. Each task has a name, used for tracing (should be UpperCamelCase). +/// FIXME(sammccall): pull out a scheduler options struct. +class TUScheduler { +public: + TUScheduler(unsigned AsyncThreadsCount, bool StorePreamblesInMemory, + PreambleParsedCallback PreambleCallback, + std::chrono::steady_clock::duration UpdateDebounce, + ASTRetentionPolicy RetentionPolicy); + ~TUScheduler(); + + /// Returns estimated memory usage for each of the currently open files. + /// The order of results is unspecified. + std::vector> getUsedBytesPerFile() const; + + /// Returns a list of files with ASTs currently stored in memory. This method + /// is not very reliable and is only used for test. E.g., the results will not + /// contain files that currently run something over their AST. + std::vector getFilesWithCachedAST() const; + + /// Schedule an update for \p File. Adds \p File to a list of tracked files if + /// \p File was not part of it before. + /// FIXME(ibiryukov): remove the callback from this function. + void update(PathRef File, ParseInputs Inputs, WantDiagnostics WD, + llvm::unique_function)> OnUpdated); + + /// Remove \p File from the list of tracked files and schedule removal of its + /// resources. + void remove(PathRef File); + + /// Schedule an async read of the AST. \p Action will be called when AST is + /// ready. The AST passed to \p Action refers to the version of \p File + /// tracked at the time of the call, even if new updates are received before + /// \p Action is executed. + /// If an error occurs during processing, it is forwarded to the \p Action + /// callback. + void runWithAST(llvm::StringRef Name, PathRef File, + Callback Action); + + /// Schedule an async read of the Preamble. + /// The preamble may be stale, generated from an older version of the file. + /// Reading from locations in the preamble may cause the files to be re-read. + /// This gives callers two options: + /// - validate that the preamble is still valid, and only use it in this case + /// - accept that preamble contents may be outdated, and try to avoid reading + /// source code from headers. + /// If there's no preamble yet (because the file was just opened), we'll wait + /// for it to build. The preamble may still be null if it fails to build or is + /// empty. + /// If an error occurs during processing, it is forwarded to the \p Action + /// callback. + void runWithPreamble(llvm::StringRef Name, PathRef File, + Callback Action); + + /// Wait until there are no scheduled or running tasks. + /// Mostly useful for synchronizing tests. + bool blockUntilIdle(Deadline D) const; + +private: + /// This class stores per-file data in the Files map. + struct FileData; + +public: + /// Responsible for retaining and rebuilding idle ASTs. An implementation is + /// an LRU cache. + class ASTCache; + +private: + const bool StorePreamblesInMemory; + const std::shared_ptr PCHOps; + const PreambleParsedCallback PreambleCallback; + Semaphore Barrier; + llvm::StringMap> Files; + std::unique_ptr IdleASTs; + // None when running tasks synchronously and non-None when running tasks + // asynchronously. + llvm::Optional PreambleTasks; + llvm::Optional WorkerThreads; + std::chrono::steady_clock::duration UpdateDebounce; +}; +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/Threading.cpp b/clangd/Threading.cpp new file mode 100644 index 000000000..aa9dd8fcb --- /dev/null +++ b/clangd/Threading.cpp @@ -0,0 +1,100 @@ +#include "Threading.h" +#include "Trace.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Threading.h" +#include + +namespace clang { +namespace clangd { + +void Notification::notify() { + { + std::lock_guard Lock(Mu); + Notified = true; + } + CV.notify_all(); +} + +void Notification::wait() const { + std::unique_lock Lock(Mu); + CV.wait(Lock, [this] { return Notified; }); +} + +Semaphore::Semaphore(std::size_t MaxLocks) : FreeSlots(MaxLocks) {} + +void Semaphore::lock() { + trace::Span Span("WaitForFreeSemaphoreSlot"); + // trace::Span can also acquire locks in ctor and dtor, we make sure it + // happens when Semaphore's own lock is not held. + { + std::unique_lock Lock(Mutex); + SlotsChanged.wait(Lock, [&]() { return FreeSlots > 0; }); + --FreeSlots; + } +} + +void Semaphore::unlock() { + std::unique_lock Lock(Mutex); + ++FreeSlots; + Lock.unlock(); + + SlotsChanged.notify_one(); +} + +AsyncTaskRunner::~AsyncTaskRunner() { wait(); } + +bool AsyncTaskRunner::wait(Deadline D) const { + std::unique_lock Lock(Mutex); + return clangd::wait(Lock, TasksReachedZero, D, + [&] { return InFlightTasks == 0; }); +} + +void AsyncTaskRunner::runAsync(const llvm::Twine &Name, + llvm::unique_function Action) { + { + std::lock_guard Lock(Mutex); + ++InFlightTasks; + } + + auto CleanupTask = llvm::make_scope_exit([this]() { + std::lock_guard Lock(Mutex); + int NewTasksCnt = --InFlightTasks; + if (NewTasksCnt == 0) { + // Note: we can't unlock here because we don't want the object to be + // destroyed before we notify. + TasksReachedZero.notify_one(); + } + }); + + std::thread( + [](std::string Name, decltype(Action) Action, decltype(CleanupTask)) { + llvm::set_thread_name(Name); + Action(); + // Make sure function stored by Action is destroyed before CleanupTask + // is run. + Action = nullptr; + }, + Name.str(), std::move(Action), std::move(CleanupTask)) + .detach(); +} + +Deadline timeoutSeconds(llvm::Optional Seconds) { + using namespace std::chrono; + if (!Seconds) + return Deadline::infinity(); + return steady_clock::now() + + duration_cast(duration(*Seconds)); +} + +void wait(std::unique_lock &Lock, std::condition_variable &CV, + Deadline D) { + if (D == Deadline::zero()) + return; + if (D == Deadline::infinity()) + return CV.wait(Lock); + CV.wait_until(Lock, D.time()); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/Threading.h b/clangd/Threading.h new file mode 100644 index 000000000..6dad841da --- /dev/null +++ b/clangd/Threading.h @@ -0,0 +1,120 @@ +//===--- ThreadPool.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_THREADING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_THREADING_H + +#include "Context.h" +#include "Function.h" +#include "llvm/ADT/Twine.h" +#include +#include +#include +#include +#include + +namespace clang { +namespace clangd { + +/// A threadsafe flag that is initially clear. +class Notification { +public: + // Sets the flag. No-op if already set. + void notify(); + // Blocks until flag is set. + void wait() const; + +private: + bool Notified = false; + mutable std::condition_variable CV; + mutable std::mutex Mu; +}; + +/// Limits the number of threads that can acquire the lock at the same time. +class Semaphore { +public: + Semaphore(std::size_t MaxLocks); + + void lock(); + void unlock(); + +private: + std::mutex Mutex; + std::condition_variable SlotsChanged; + std::size_t FreeSlots; +}; + +/// A point in time we can wait for. +/// Can be zero (don't wait) or infinity (wait forever). +/// (Not time_point::max(), because many std::chrono implementations overflow). +class Deadline { +public: + Deadline(std::chrono::steady_clock::time_point Time) + : Type(Finite), Time(Time) {} + static Deadline zero() { return Deadline(Zero); } + static Deadline infinity() { return Deadline(Infinite); } + + std::chrono::steady_clock::time_point time() const { + assert(Type == Finite); + return Time; + } + bool expired() const { + return (Type == Zero) || + (Type == Finite && Time < std::chrono::steady_clock::now()); + } + bool operator==(const Deadline &Other) const { + return (Type == Other.Type) && (Type != Finite || Time == Other.Time); + } + +private: + enum Type { Zero, Infinite, Finite }; + + Deadline(enum Type Type) : Type(Type) {} + enum Type Type; + std::chrono::steady_clock::time_point Time; +}; + +/// Makes a deadline from a timeout in seconds. None means wait forever. +Deadline timeoutSeconds(llvm::Optional Seconds); +/// Wait once on CV for the specified duration. +void wait(std::unique_lock &Lock, std::condition_variable &CV, + Deadline D); +/// Waits on a condition variable until F() is true or D expires. +template +LLVM_NODISCARD bool wait(std::unique_lock &Lock, + std::condition_variable &CV, Deadline D, Func F) { + while (!F()) { + if (D.expired()) + return false; + wait(Lock, CV, D); + } + return true; +} + +/// Runs tasks on separate (detached) threads and wait for all tasks to finish. +/// Objects that need to spawn threads can own an AsyncTaskRunner to ensure they +/// all complete on destruction. +class AsyncTaskRunner { +public: + /// Destructor waits for all pending tasks to finish. + ~AsyncTaskRunner(); + + void wait() const { (void)wait(Deadline::infinity()); } + LLVM_NODISCARD bool wait(Deadline D) const; + // The name is used for tracing and debugging (e.g. to name a spawned thread). + void runAsync(const llvm::Twine &Name, llvm::unique_function Action); + +private: + mutable std::mutex Mutex; + mutable std::condition_variable TasksReachedZero; + std::size_t InFlightTasks = 0; +}; +} // namespace clangd +} // namespace clang +#endif diff --git a/clangd/Trace.cpp b/clangd/Trace.cpp new file mode 100644 index 000000000..24c1fdd74 --- /dev/null +++ b/clangd/Trace.cpp @@ -0,0 +1,233 @@ +//===--- Trace.cpp - Performance tracing facilities -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Trace.h" +#include "Context.h" +#include "Function.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/FormatProviders.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Threading.h" +#include +#include + +namespace clang { +namespace clangd { +namespace trace { +using namespace llvm; + +namespace { +// The current implementation is naive: each thread writes to Out guarded by Mu. +// Perhaps we should replace this by something that disturbs performance less. +class JSONTracer : public EventTracer { +public: + JSONTracer(raw_ostream &Out, bool Pretty) + : Out(Out), Sep(""), Start(std::chrono::system_clock::now()), + JSONFormat(Pretty ? "{0:2}" : "{0}") { + // The displayTimeUnit must be ns to avoid low-precision overlap + // calculations! + Out << R"({"displayTimeUnit":"ns","traceEvents":[)" + << "\n"; + rawEvent("M", json::Object{ + {"name", "process_name"}, + {"args", json::Object{{"name", "clangd"}}}, + }); + } + + ~JSONTracer() { + Out << "\n]}"; + Out.flush(); + } + + // We stash a Span object in the context. It will record the start/end, + // and this also allows us to look up the parent Span's information. + Context beginSpan(llvm::StringRef Name, json::Object *Args) override { + return Context::current().derive( + SpanKey, llvm::make_unique(this, Name, Args)); + } + + // Trace viewer requires each thread to properly stack events. + // So we need to mark only duration that the span was active on the thread. + // (Hopefully any off-thread activity will be connected by a flow event). + // Record the end time here, but don't write the event: Args aren't ready yet. + void endSpan() override { + Context::current().getExisting(SpanKey)->markEnded(); + } + + void instant(llvm::StringRef Name, json::Object &&Args) override { + captureThreadMetadata(); + jsonEvent("i", json::Object{{"name", Name}, {"args", std::move(Args)}}); + } + + // Record an event on the current thread. ph, pid, tid, ts are set. + // Contents must be a list of the other JSON key/values. + void jsonEvent(StringRef Phase, json::Object &&Contents, + uint64_t TID = get_threadid(), double Timestamp = 0) { + Contents["ts"] = Timestamp ? Timestamp : timestamp(); + Contents["tid"] = int64_t(TID); + std::lock_guard Lock(Mu); + rawEvent(Phase, std::move(Contents)); + } + +private: + class JSONSpan { + public: + JSONSpan(JSONTracer *Tracer, llvm::StringRef Name, json::Object *Args) + : StartTime(Tracer->timestamp()), EndTime(0), Name(Name), + TID(get_threadid()), Tracer(Tracer), Args(Args) { + // ~JSONSpan() may run in a different thread, so we need to capture now. + Tracer->captureThreadMetadata(); + + // We don't record begin events here (and end events in the destructor) + // because B/E pairs have to appear in the right order, which is awkward. + // Instead we send the complete (X) event in the destructor. + + // If our parent was on a different thread, add an arrow to this span. + auto *Parent = Context::current().get(SpanKey); + if (Parent && *Parent && (*Parent)->TID != TID) { + // If the parent span ended already, then show this as "following" it. + // Otherwise show us as "parallel". + double OriginTime = (*Parent)->EndTime; + if (!OriginTime) + OriginTime = (*Parent)->StartTime; + + auto FlowID = nextID(); + Tracer->jsonEvent("s", + json::Object{{"id", FlowID}, + {"name", "Context crosses threads"}, + {"cat", "dummy"}}, + (*Parent)->TID, (*Parent)->StartTime); + Tracer->jsonEvent("f", + json::Object{{"id", FlowID}, + {"bp", "e"}, + {"name", "Context crosses threads"}, + {"cat", "dummy"}}, + TID); + } + } + + ~JSONSpan() { + // Finally, record the event (ending at EndTime, not timestamp())! + Tracer->jsonEvent("X", + json::Object{{"name", std::move(Name)}, + {"args", std::move(*Args)}, + {"dur", EndTime - StartTime}}, + TID, StartTime); + } + + // May be called by any thread. + void markEnded() { + EndTime = Tracer->timestamp(); + } + + private: + static int64_t nextID() { + static std::atomic Next = {0}; + return Next++; + } + + double StartTime; + std::atomic EndTime; // Filled in by markEnded(). + std::string Name; + uint64_t TID; + JSONTracer *Tracer; + json::Object *Args; + }; + static Key> SpanKey; + + // Record an event. ph and pid are set. + // Contents must be a list of the other JSON key/values. + void rawEvent(StringRef Phase, json::Object &&Event) /*REQUIRES(Mu)*/ { + // PID 0 represents the clangd process. + Event["pid"] = 0; + Event["ph"] = Phase; + Out << Sep << formatv(JSONFormat, json::Value(std::move(Event))); + Sep = ",\n"; + } + + // If we haven't already, emit metadata describing this thread. + void captureThreadMetadata() { + uint64_t TID = get_threadid(); + std::lock_guard Lock(Mu); + if (ThreadsWithMD.insert(TID).second) { + SmallString<32> Name; + get_thread_name(Name); + if (!Name.empty()) { + rawEvent("M", json::Object{ + {"tid", int64_t(TID)}, + {"name", "thread_name"}, + {"args", json::Object{{"name", Name}}}, + }); + } + } + } + + double timestamp() { + using namespace std::chrono; + return duration(system_clock::now() - Start).count(); + } + + std::mutex Mu; + raw_ostream &Out /*GUARDED_BY(Mu)*/; + const char *Sep /*GUARDED_BY(Mu)*/; + DenseSet ThreadsWithMD /*GUARDED_BY(Mu)*/; + const sys::TimePoint<> Start; + const char *JSONFormat; +}; + +Key> JSONTracer::SpanKey; + +EventTracer *T = nullptr; +} // namespace + +Session::Session(EventTracer &Tracer) { + assert(!T && "Resetting global tracer is not allowed."); + T = &Tracer; +} + +Session::~Session() { T = nullptr; } + +std::unique_ptr createJSONTracer(llvm::raw_ostream &OS, + bool Pretty) { + return llvm::make_unique(OS, Pretty); +} + +void log(const Twine &Message) { + if (!T) + return; + T->instant("Log", json::Object{{"Message", Message.str()}}); +} + +// Returned context owns Args. +static Context makeSpanContext(llvm::Twine Name, json::Object *Args) { + if (!T) + return Context::current().clone(); + WithContextValue WithArgs{std::unique_ptr(Args)}; + return T->beginSpan(Name.isSingleStringRef() ? Name.getSingleStringRef() + : llvm::StringRef(Name.str()), + Args); +} + +// Span keeps a non-owning pointer to the args, which is how users access them. +// The args are owned by the context though. They stick around until the +// beginSpan() context is destroyed, when the tracing engine will consume them. +Span::Span(llvm::Twine Name) + : Args(T ? new json::Object() : nullptr), + RestoreCtx(makeSpanContext(Name, Args)) {} + +Span::~Span() { + if (T) + T->endSpan(); +} + +} // namespace trace +} // namespace clangd +} // namespace clang diff --git a/clangd/Trace.h b/clangd/Trace.h new file mode 100644 index 000000000..0c4c46185 --- /dev/null +++ b/clangd/Trace.h @@ -0,0 +1,108 @@ +//===--- Trace.h - Performance tracing facilities ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Supports writing performance traces describing clangd's behavior. +// Traces are consumed by implementations of the EventTracer interface. +// +// +// All APIs are no-ops unless a Session is active (created by ClangdMain). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRACE_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRACE_H_ + +#include "Context.h" +#include "Function.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +namespace trace { + +/// A consumer of trace events. The events are produced by Spans and trace::log. +/// Implmentations of this interface must be thread-safe. +class EventTracer { +public: + virtual ~EventTracer() = default; + + /// Called when event that has a duration starts. \p Name describes the event. + /// Returns a derived context that will be destroyed when the event ends. + /// Usually implementations will store an object in the returned context + /// whose destructor records the end of the event. + /// The args are *Args, only complete when the event ends. + virtual Context beginSpan(llvm::StringRef Name, llvm::json::Object *Args) = 0; + // Called when a Span is destroyed (it may still be active on other threads). + // beginSpan() and endSpan() will always form a proper stack on each thread. + // The Context returned by beginSpan is active, but Args is not ready. + // Tracers should not override this unless they need to observe strict + // per-thread nesting. Instead they should observe context destruction. + virtual void endSpan(){}; + + /// Called for instant events. + virtual void instant(llvm::StringRef Name, llvm::json::Object &&Args) = 0; +}; + +/// Sets up a global EventTracer that consumes events produced by Span and +/// trace::log. Only one TracingSession can be active at a time and it should be +/// set up before calling any clangd-specific functions. +class Session { +public: + Session(EventTracer &Tracer); + ~Session(); +}; + +/// Create an instance of EventTracer that produces an output in the Trace Event +/// format supported by Chrome's trace viewer (chrome://tracing). +/// +/// The format is documented here: +/// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview +std::unique_ptr createJSONTracer(llvm::raw_ostream &OS, + bool Pretty = false); + +/// Records a single instant event, associated with the current thread. +void log(const llvm::Twine &Name); + +/// Records an event whose duration is the lifetime of the Span object. +/// This lifetime is extended when the span's context is reused. +/// +/// This is the main public interface for producing tracing events. +/// +/// Arbitrary JSON metadata can be attached while this span is active: +/// SPAN_ATTACH(MySpan, "Payload", SomeJSONExpr); +/// +/// SomeJSONExpr is evaluated and copied only if actually needed. +class Span { +public: + Span(llvm::Twine Name); + ~Span(); + + /// Mutable metadata, if this span is interested. + /// Prefer to use SPAN_ATTACH rather than accessing this directly. + llvm::json::Object *const Args; + +private: + WithContext RestoreCtx; +}; + +/// Attach a key-value pair to a Span event. +/// This is not threadsafe when used with the same Span. +#define SPAN_ATTACH(S, Name, Expr) \ + do { \ + if (auto *Args = (S).Args) \ + (*Args)[Name] = Expr; \ + } while (0) + +} // namespace trace +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRACE_H_ diff --git a/clangd/URI.cpp b/clangd/URI.cpp new file mode 100644 index 000000000..a72264089 --- /dev/null +++ b/clangd/URI.cpp @@ -0,0 +1,207 @@ +//===---- URI.h - File URIs with schemes -------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "URI.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/Path.h" +#include +#include + +LLVM_INSTANTIATE_REGISTRY(clang::clangd::URISchemeRegistry) + +namespace clang { +namespace clangd { +namespace { + +inline llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error(Message, + llvm::inconvertibleErrorCode()); +} + +/// \brief This manages file paths in the file system. All paths in the scheme +/// are absolute (with leading '/'). +class FileSystemScheme : public URIScheme { +public: + static const char *Scheme; + + llvm::Expected + getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, + llvm::StringRef /*HintPath*/) const override { + if (!Body.startswith("/")) + return make_string_error("File scheme: expect body to be an absolute " + "path starting with '/': " + + Body); + // For Windows paths e.g. /X: + if (Body.size() > 2 && Body[0] == '/' && Body[2] == ':') + Body.consume_front("/"); + llvm::SmallVector Path(Body.begin(), Body.end()); + llvm::sys::path::native(Path); + return std::string(Path.begin(), Path.end()); + } + + llvm::Expected + uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { + using namespace llvm::sys; + + std::string Body; + // For Windows paths e.g. X: + if (AbsolutePath.size() > 1 && AbsolutePath[1] == ':') + Body = "/"; + Body += path::convert_to_slash(AbsolutePath); + return URI(Scheme, /*Authority=*/"", Body); + } +}; + +const char *FileSystemScheme::Scheme = "file"; + +static URISchemeRegistry::Add + X(FileSystemScheme::Scheme, + "URI scheme for absolute paths in the file system."); + +llvm::Expected> +findSchemeByName(llvm::StringRef Scheme) { + for (auto I = URISchemeRegistry::begin(), E = URISchemeRegistry::end(); + I != E; ++I) { + if (I->getName() != Scheme) + continue; + return I->instantiate(); + } + return make_string_error("Can't find scheme: " + Scheme); +} + +bool shouldEscape(unsigned char C) { + // Unreserved characters. + if ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || + (C >= '0' && C <= '9')) + return false; + switch (C) { + case '-': + case '_': + case '.': + case '~': + case '/': // '/' is only reserved when parsing. + return false; + } + return true; +} + +/// Encodes a string according to percent-encoding. +/// - Unreserved characters are not escaped. +/// - Reserved characters always escaped with exceptions like '/'. +/// - All other characters are escaped. +std::string percentEncode(llvm::StringRef Content) { + std::string Result; + llvm::raw_string_ostream OS(Result); + for (unsigned char C : Content) + if (shouldEscape(C)) + OS << '%' << llvm::format_hex_no_prefix(C, 2); + else + OS << C; + + OS.flush(); + return Result; +} + +/// Decodes a string according to percent-encoding. +std::string percentDecode(llvm::StringRef Content) { + std::string Result; + for (auto I = Content.begin(), E = Content.end(); I != E; ++I) { + if (*I != '%') { + Result += *I; + continue; + } + if (*I == '%' && I + 2 < Content.end() && llvm::isHexDigit(*(I + 1)) && + llvm::isHexDigit(*(I + 2))) { + Result.push_back(llvm::hexFromNibbles(*(I + 1), *(I + 2))); + I += 2; + } else + Result.push_back(*I); + } + return Result; +} + +} // namespace + +URI::URI(llvm::StringRef Scheme, llvm::StringRef Authority, + llvm::StringRef Body) + : Scheme(Scheme), Authority(Authority), Body(Body) { + assert(!Scheme.empty()); + assert((Authority.empty() || Body.startswith("/")) && + "URI body must start with '/' when authority is present."); +} + +std::string URI::toString() const { + std::string Result; + llvm::raw_string_ostream OS(Result); + OS << percentEncode(Scheme) << ":"; + if (Authority.empty() && Body.empty()) + return OS.str(); + // If authority if empty, we only print body if it starts with "/"; otherwise, + // the URI is invalid. + if (!Authority.empty() || llvm::StringRef(Body).startswith("/")) + OS << "//" << percentEncode(Authority); + OS << percentEncode(Body); + OS.flush(); + return Result; +} + +llvm::Expected URI::parse(llvm::StringRef OrigUri) { + URI U; + llvm::StringRef Uri = OrigUri; + + auto Pos = Uri.find(':'); + if (Pos == 0 || Pos == llvm::StringRef::npos) + return make_string_error("Scheme must be provided in URI: " + OrigUri); + U.Scheme = percentDecode(Uri.substr(0, Pos)); + Uri = Uri.substr(Pos + 1); + if (Uri.consume_front("//")) { + Pos = Uri.find('/'); + U.Authority = percentDecode(Uri.substr(0, Pos)); + Uri = Uri.substr(Pos); + } + U.Body = percentDecode(Uri); + return U; +} + +llvm::Expected URI::create(llvm::StringRef AbsolutePath, + llvm::StringRef Scheme) { + if (!llvm::sys::path::is_absolute(AbsolutePath)) + return make_string_error("Not a valid absolute path: " + AbsolutePath); + auto S = findSchemeByName(Scheme); + if (!S) + return S.takeError(); + return S->get()->uriFromAbsolutePath(AbsolutePath); +} + +URI URI::createFile(llvm::StringRef AbsolutePath) { + auto U = create(AbsolutePath, "file"); + if (!U) + llvm_unreachable(llvm::toString(U.takeError()).c_str()); + return std::move(*U); +} + +llvm::Expected URI::resolve(const URI &Uri, + llvm::StringRef HintPath) { + auto S = findSchemeByName(Uri.Scheme); + if (!S) + return S.takeError(); + return S->get()->getAbsolutePath(Uri.Authority, Uri.Body, HintPath); +} + +llvm::Expected URI::includeSpelling(const URI &Uri) { + auto S = findSchemeByName(Uri.Scheme); + if (!S) + return S.takeError(); + return S->get()->getIncludeSpelling(Uri); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/URI.h b/clangd/URI.h new file mode 100644 index 000000000..112264f71 --- /dev/null +++ b/clangd/URI.h @@ -0,0 +1,122 @@ +//===--- URI.h - File URIs with schemes --------------------------*- 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_PATHURI_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATHURI_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Registry.h" + +namespace clang { +namespace clangd { + +/// A URI describes the location of a source file. +/// In the simplest case, this is a "file" URI that directly encodes the +/// absolute path to a file. More abstract cases are possible: a shared index +/// service might expose repo:// URIs that are relative to the source control +/// root. +/// +/// Clangd handles URIs of the form :[//]. It doesn't +/// further split the authority or body into constituent parts (e.g. query +/// strings is included in the body). +class URI { +public: + URI(llvm::StringRef Scheme, llvm::StringRef Authority, llvm::StringRef Body); + + /// Returns decoded scheme e.g. "https" + llvm::StringRef scheme() const { return Scheme; } + /// Returns decoded authority e.g. "reviews.lvm.org" + llvm::StringRef authority() const { return Authority; } + /// Returns decoded body e.g. "/D41946" + llvm::StringRef body() const { return Body; } + + /// Returns a string URI with all components percent-encoded. + std::string toString() const; + + /// Creates a URI for a file in the given scheme. \p Scheme must be + /// registered. The URI is percent-encoded. + static llvm::Expected create(llvm::StringRef AbsolutePath, + llvm::StringRef Scheme); + + /// This creates a file:// URI for \p AbsolutePath. The path must be absolute. + static URI createFile(llvm::StringRef AbsolutePath); + + /// Parse a URI string ":[///]". Percent-encoded + /// characters in the URI will be decoded. + static llvm::Expected parse(llvm::StringRef Uri); + + /// Resolves the absolute path of \p U. If there is no matching scheme, or the + /// URI is invalid in the scheme, this returns an error. + /// + /// \p HintPath A related path, such as the current file or working directory, + /// which can help disambiguate when the same file exists in many workspaces. + static llvm::Expected resolve(const URI &U, + llvm::StringRef HintPath = ""); + + /// Gets the preferred spelling of this file for #include, if there is one, + /// e.g. , "path/to/x.h". + /// + /// This allows URI schemas to provide their customized include paths. + /// + /// Returns an empty string if normal include-shortening based on the absolute + /// path should be used. + /// Fails if the URI is not valid in the schema. + static llvm::Expected includeSpelling(const URI &U); + + friend bool operator==(const URI &LHS, const URI &RHS) { + return std::tie(LHS.Scheme, LHS.Authority, LHS.Body) == + std::tie(RHS.Scheme, RHS.Authority, RHS.Body); + } + + friend bool operator<(const URI &LHS, const URI &RHS) { + return std::tie(LHS.Scheme, LHS.Authority, LHS.Body) < + std::tie(RHS.Scheme, RHS.Authority, RHS.Body); + } + +private: + URI() = default; + + std::string Scheme; + std::string Authority; + std::string Body; +}; + +/// URIScheme is an extension point for teaching clangd to recognize a custom +/// URI scheme. This is expected to be implemented and exposed via the +/// URISchemeRegistry. +class URIScheme { +public: + virtual ~URIScheme() = default; + + /// Returns the absolute path of the file corresponding to the URI + /// authority+body in the file system. See URI::resolve for semantics of + /// \p HintPath. + virtual llvm::Expected + getAbsolutePath(llvm::StringRef Authority, llvm::StringRef Body, + llvm::StringRef HintPath) const = 0; + + virtual llvm::Expected + uriFromAbsolutePath(llvm::StringRef AbsolutePath) const = 0; + + /// Returns the include path of the file (e.g. , "path"), which can be + /// #included directly. See URI::includeSpelling for details. + virtual llvm::Expected getIncludeSpelling(const URI &U) const { + return ""; // no customized include path for this scheme. + } +}; + +/// By default, a "file" scheme is supported where URI paths are always absolute +/// in the file system. +typedef llvm::Registry URISchemeRegistry; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_PATHURI_H diff --git a/clangd/XRefs.cpp b/clangd/XRefs.cpp new file mode 100644 index 000000000..99ccd21f1 --- /dev/null +++ b/clangd/XRefs.cpp @@ -0,0 +1,673 @@ +//===--- XRefs.cpp ----------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +#include "XRefs.h" +#include "AST.h" +#include "Logger.h" +#include "SourceCode.h" +#include "URI.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/Support/Path.h" +namespace clang { +namespace clangd { +using namespace llvm; +namespace { + +// Get the definition from a given declaration `D`. +// Return nullptr if no definition is found, or the declaration type of `D` is +// not supported. +const Decl *getDefinition(const Decl *D) { + assert(D); + if (const auto *TD = dyn_cast(D)) + return TD->getDefinition(); + else if (const auto *VD = dyn_cast(D)) + return VD->getDefinition(); + else if (const auto *FD = dyn_cast(D)) + return FD->getDefinition(); + return nullptr; +} + +// Convert a SymbolLocation to LSP's Location. +// HintPath is used to resolve the path of URI. +// FIXME: figure out a good home for it, and share the implementation with +// FindSymbols. +llvm::Optional toLSPLocation(const SymbolLocation &Loc, + llvm::StringRef HintPath) { + if (!Loc) + return llvm::None; + auto Uri = URI::parse(Loc.FileURI); + if (!Uri) { + log("Could not parse URI: {0}", Loc.FileURI); + return llvm::None; + } + auto Path = URI::resolve(*Uri, HintPath); + if (!Path) { + log("Could not resolve URI: {0}", Loc.FileURI); + return llvm::None; + } + Location LSPLoc; + LSPLoc.uri = URIForFile(*Path); + LSPLoc.range.start.line = Loc.Start.Line; + LSPLoc.range.start.character = Loc.Start.Column; + LSPLoc.range.end.line = Loc.End.Line; + LSPLoc.range.end.character = Loc.End.Column; + return LSPLoc; +} + +struct MacroDecl { + StringRef Name; + const MacroInfo *Info; +}; + +/// Finds declarations locations that a given source location refers to. +class DeclarationAndMacrosFinder : public index::IndexDataConsumer { + std::vector Decls; + std::vector MacroInfos; + const SourceLocation &SearchedLocation; + const ASTContext &AST; + Preprocessor &PP; + +public: + DeclarationAndMacrosFinder(raw_ostream &OS, + const SourceLocation &SearchedLocation, + ASTContext &AST, Preprocessor &PP) + : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} + + std::vector takeDecls() { + // Don't keep the same declaration multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(Decls.begin(), Decls.end()); + auto Last = std::unique(Decls.begin(), Decls.end()); + Decls.erase(Last, Decls.end()); + return std::move(Decls); + } + + std::vector takeMacroInfos() { + // Don't keep the same Macro info multiple times. + std::sort(MacroInfos.begin(), MacroInfos.end(), + [](const MacroDecl &Left, const MacroDecl &Right) { + return Left.Info < Right.Info; + }); + + auto Last = std::unique(MacroInfos.begin(), MacroInfos.end(), + [](const MacroDecl &Left, const MacroDecl &Right) { + return Left.Info == Right.Info; + }); + MacroInfos.erase(Last, MacroInfos.end()); + return std::move(MacroInfos); + } + + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + if (Loc == SearchedLocation) { + // Find and add definition declarations (for GoToDefinition). + // We don't use parameter `D`, as Parameter `D` is the canonical + // declaration, which is the first declaration of a redeclarable + // declaration, and it could be a forward declaration. + if (const auto *Def = getDefinition(D)) { + Decls.push_back(Def); + } else { + // Couldn't find a definition, fall back to use `D`. + Decls.push_back(D); + } + } + return true; + } + +private: + void finish() override { + // Also handle possible macro at the searched location. + Token Result; + auto &Mgr = AST.getSourceManager(); + if (!Lexer::getRawToken(Mgr.getSpellingLoc(SearchedLocation), Result, Mgr, + AST.getLangOpts(), false)) { + if (Result.is(tok::raw_identifier)) { + PP.LookUpIdentifierInfo(Result); + } + IdentifierInfo *IdentifierInfo = Result.getIdentifierInfo(); + if (IdentifierInfo && IdentifierInfo->hadMacroDefinition()) { + std::pair DecLoc = + Mgr.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 = Mgr.getMacroArgExpandedLocation( + Mgr.getLocForStartOfFile(DecLoc.first) + .getLocWithOffset(DecLoc.second - 1)); + MacroDefinition MacroDef = + PP.getMacroDefinitionAtLoc(IdentifierInfo, BeforeSearchedLocation); + MacroInfo *MacroInf = MacroDef.getMacroInfo(); + if (MacroInf) { + MacroInfos.push_back(MacroDecl{IdentifierInfo->getName(), MacroInf}); + assert(Decls.empty()); + } + } + } + } +}; + +struct IdentifiedSymbol { + std::vector Decls; + std::vector Macros; +}; + +IdentifiedSymbol getSymbolAtPosition(ParsedAST &AST, SourceLocation Pos) { + auto DeclMacrosFinder = DeclarationAndMacrosFinder( + llvm::errs(), Pos, AST.getASTContext(), AST.getPreprocessor()); + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(), + DeclMacrosFinder, IndexOpts); + + return {DeclMacrosFinder.takeDecls(), DeclMacrosFinder.takeMacroInfos()}; +} + +llvm::Optional +makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + const LangOptions &LangOpts = AST.getASTContext().getLangOpts(); + SourceLocation LocStart = ValSourceRange.getBegin(); + + const FileEntry *F = + SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart)); + if (!F) + return llvm::None; + SourceLocation LocEnd = Lexer::getLocForEndOfToken(ValSourceRange.getEnd(), 0, + SourceMgr, LangOpts); + Position Begin = sourceLocToPosition(SourceMgr, LocStart); + Position End = sourceLocToPosition(SourceMgr, LocEnd); + Range R = {Begin, End}; + Location L; + + auto FilePath = getAbsoluteFilePath(F, SourceMgr); + if (!FilePath) { + log("failed to get path!"); + return llvm::None; + } + L.uri = URIForFile(*FilePath); + L.range = R; + return L; +} + +// Get the symbol ID for a declaration, if possible. +llvm::Optional getSymbolID(const Decl *D) { + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(D, USR)) { + return None; + } + return SymbolID(USR); +} + +} // namespace + +std::vector findDefinitions(ParsedAST &AST, Position Pos, + const SymbolIndex *Index) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + + std::vector Result; + // Handle goto definition for #include. + for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) { + if (!Inc.Resolved.empty() && Inc.R.contains(Pos)) + Result.push_back(Location{URIForFile{Inc.Resolved}, {}}); + } + if (!Result.empty()) + return Result; + + // Identified symbols at a specific position. + SourceLocation SourceLocationBeg = + getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg); + + for (auto Item : Symbols.Macros) { + auto Loc = Item.Info->getDefinitionLoc(); + auto L = makeLocation(AST, SourceRange(Loc, Loc)); + if (L) + Result.push_back(*L); + } + + // Declaration and definition are different terms in C-family languages, and + // LSP only defines the "GoToDefinition" specification, so we try to perform + // the "most sensible" GoTo operation: + // + // - We use the location from AST and index (if available) to provide the + // final results. When there are duplicate results, we prefer AST over + // index because AST is more up-to-date. + // + // - For each symbol, we will return a location of the canonical declaration + // (e.g. function declaration in header), and a location of definition if + // they are available. + // + // So the work flow: + // + // 1. Identify the symbols being search for by traversing the AST. + // 2. Populate one of the locations with the AST location. + // 3. Use the AST information to query the index, and populate the index + // location (if available). + // 4. Return all populated locations for all symbols, definition first ( + // which we think is the users wants most often). + struct CandidateLocation { + llvm::Optional Def; + llvm::Optional Decl; + }; + llvm::DenseMap ResultCandidates; + + // Emit all symbol locations (declaration or definition) from AST. + for (const auto *D : Symbols.Decls) { + // Fake key for symbols don't have USR (no SymbolID). + // Ideally, there should be a USR for each identified symbols. Symbols + // without USR are rare and unimportant cases, we use the a fake holder to + // minimize the invasiveness of these cases. + SymbolID Key(""); + if (auto ID = getSymbolID(D)) + Key = *ID; + + auto &Candidate = ResultCandidates[Key]; + auto Loc = findNameLoc(D); + auto L = makeLocation(AST, SourceRange(Loc, Loc)); + // The declaration in the identified symbols is a definition if possible + // otherwise it is declaration. + bool IsDef = getDefinition(D) == D; + // Populate one of the slots with location for the AST. + if (!IsDef) + Candidate.Decl = L; + else + Candidate.Def = L; + } + + if (Index) { + LookupRequest QueryRequest; + // Build request for index query, using SymbolID. + for (auto It : ResultCandidates) + QueryRequest.IDs.insert(It.first); + std::string HintPath; + const FileEntry *FE = + SourceMgr.getFileEntryForID(SourceMgr.getMainFileID()); + if (auto Path = getAbsoluteFilePath(FE, SourceMgr)) + HintPath = *Path; + // Query the index and populate the empty slot. + Index->lookup( + QueryRequest, [&HintPath, &ResultCandidates](const Symbol &Sym) { + auto It = ResultCandidates.find(Sym.ID); + assert(It != ResultCandidates.end()); + auto &Value = It->second; + + if (!Value.Def) + Value.Def = toLSPLocation(Sym.Definition, HintPath); + if (!Value.Decl) + Value.Decl = toLSPLocation(Sym.CanonicalDeclaration, HintPath); + }); + } + + // Populate the results, definition first. + for (auto It : ResultCandidates) { + const auto &Candidate = It.second; + if (Candidate.Def) + Result.push_back(*Candidate.Def); + if (Candidate.Decl && + Candidate.Decl != Candidate.Def) // Decl and Def might be the same + Result.push_back(*Candidate.Decl); + } + + return Result; +} + +namespace { + +/// Finds document highlights that a given list of declarations refers to. +class DocumentHighlightsFinder : public index::IndexDataConsumer { + std::vector &Decls; + std::vector DocumentHighlights; + const ASTContext &AST; + +public: + DocumentHighlightsFinder(raw_ostream &OS, ASTContext &AST, Preprocessor &PP, + std::vector &Decls) + : Decls(Decls), AST(AST) {} + std::vector takeHighlights() { + // Don't keep the same highlight multiple times. + // This can happen when nodes in the AST are visited twice. + std::sort(DocumentHighlights.begin(), DocumentHighlights.end()); + auto Last = + std::unique(DocumentHighlights.begin(), DocumentHighlights.end()); + DocumentHighlights.erase(Last, DocumentHighlights.end()); + return std::move(DocumentHighlights); + } + + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + const SourceManager &SourceMgr = AST.getSourceManager(); + SourceLocation HighlightStartLoc = SourceMgr.getFileLoc(Loc); + if (SourceMgr.getMainFileID() != SourceMgr.getFileID(HighlightStartLoc) || + std::find(Decls.begin(), Decls.end(), D) == Decls.end()) { + return true; + } + SourceLocation End; + const LangOptions &LangOpts = AST.getLangOpts(); + End = Lexer::getLocForEndOfToken(HighlightStartLoc, 0, SourceMgr, LangOpts); + SourceRange SR(HighlightStartLoc, End); + + DocumentHighlightKind Kind = DocumentHighlightKind::Text; + if (static_cast(index::SymbolRole::Write) & Roles) + Kind = DocumentHighlightKind::Write; + else if (static_cast(index::SymbolRole::Read) & Roles) + Kind = DocumentHighlightKind::Read; + + DocumentHighlights.push_back(getDocumentHighlight(SR, Kind)); + return true; + } + +private: + DocumentHighlight getDocumentHighlight(SourceRange SR, + DocumentHighlightKind Kind) { + const SourceManager &SourceMgr = AST.getSourceManager(); + Position Begin = sourceLocToPosition(SourceMgr, SR.getBegin()); + Position End = sourceLocToPosition(SourceMgr, SR.getEnd()); + Range R = {Begin, End}; + DocumentHighlight DH; + DH.range = R; + DH.kind = Kind; + return DH; + } +}; + +} // namespace + +std::vector findDocumentHighlights(ParsedAST &AST, + Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + SourceLocation SourceLocationBeg = + getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + + auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg); + std::vector SelectedDecls = Symbols.Decls; + + DocumentHighlightsFinder DocHighlightsFinder( + llvm::errs(), AST.getASTContext(), AST.getPreprocessor(), SelectedDecls); + + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; + indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(), + DocHighlightsFinder, IndexOpts); + + return DocHighlightsFinder.takeHighlights(); +} + +static PrintingPolicy printingPolicyForDecls(PrintingPolicy Base) { + PrintingPolicy Policy(Base); + + Policy.AnonymousTagLocations = false; + Policy.TerseOutput = true; + Policy.PolishForDeclaration = true; + Policy.ConstantsAsWritten = true; + Policy.SuppressTagKeyword = false; + + return Policy; +} + +/// Return a string representation (e.g. "class MyNamespace::MyClass") of +/// the type declaration \p TD. +static std::string typeDeclToString(const TypeDecl *TD) { + QualType Type = TD->getASTContext().getTypeDeclType(TD); + + PrintingPolicy Policy = + printingPolicyForDecls(TD->getASTContext().getPrintingPolicy()); + + std::string Name; + llvm::raw_string_ostream Stream(Name); + Type.print(Stream, Policy); + + return Stream.str(); +} + +/// Return a string representation (e.g. "namespace ns1::ns2") of +/// the named declaration \p ND. +static std::string namedDeclQualifiedName(const NamedDecl *ND, + StringRef Prefix) { + PrintingPolicy Policy = + printingPolicyForDecls(ND->getASTContext().getPrintingPolicy()); + + std::string Name; + llvm::raw_string_ostream Stream(Name); + Stream << Prefix << ' '; + ND->printQualifiedName(Stream, Policy); + + return Stream.str(); +} + +/// Given a declaration \p D, return a human-readable string representing the +/// scope in which it is declared. If the declaration is in the global scope, +/// return the string "global namespace". +static llvm::Optional getScopeName(const Decl *D) { + const DeclContext *DC = D->getDeclContext(); + + if (isa(DC)) + return std::string("global namespace"); + if (const TypeDecl *TD = dyn_cast(DC)) + return typeDeclToString(TD); + else if (const NamespaceDecl *ND = dyn_cast(DC)) + return namedDeclQualifiedName(ND, "namespace"); + else if (const FunctionDecl *FD = dyn_cast(DC)) + return namedDeclQualifiedName(FD, "function"); + + return llvm::None; +} + +/// Generate a \p Hover object given the declaration \p D. +static Hover getHoverContents(const Decl *D) { + Hover H; + llvm::Optional NamedScope = getScopeName(D); + + // Generate the "Declared in" section. + if (NamedScope) { + assert(!NamedScope->empty()); + + H.contents.value += "Declared in "; + H.contents.value += *NamedScope; + H.contents.value += "\n\n"; + } + + // We want to include the template in the Hover. + if (TemplateDecl *TD = D->getDescribedTemplate()) + D = TD; + + std::string DeclText; + llvm::raw_string_ostream OS(DeclText); + + PrintingPolicy Policy = + printingPolicyForDecls(D->getASTContext().getPrintingPolicy()); + + D->print(OS, Policy); + + OS.flush(); + + H.contents.value += DeclText; + return H; +} + +/// Generate a \p Hover object given the type \p T. +static Hover getHoverContents(QualType T, ASTContext &ASTCtx) { + Hover H; + std::string TypeText; + llvm::raw_string_ostream OS(TypeText); + PrintingPolicy Policy = printingPolicyForDecls(ASTCtx.getPrintingPolicy()); + T.print(OS, Policy); + OS.flush(); + H.contents.value += TypeText; + return H; +} + +/// Generate a \p Hover object given the macro \p MacroInf. +static Hover getHoverContents(StringRef MacroName) { + Hover H; + + H.contents.value = "#define "; + H.contents.value += MacroName; + + return H; +} + +namespace { +/// Computes the deduced type at a given location by visiting the relevant +/// nodes. We use this to display the actual type when hovering over an "auto" +/// keyword or "decltype()" expression. +/// FIXME: This could have been a lot simpler by visiting AutoTypeLocs but it +/// seems that the AutoTypeLocs that can be visited along with their AutoType do +/// not have the deduced type set. Instead, we have to go to the appropriate +/// DeclaratorDecl/FunctionDecl and work our back to the AutoType that does have +/// a deduced type set. The AST should be improved to simplify this scenario. +class DeducedTypeVisitor : public RecursiveASTVisitor { + SourceLocation SearchedLocation; + llvm::Optional DeducedType; + +public: + DeducedTypeVisitor(SourceLocation SearchedLocation) + : SearchedLocation(SearchedLocation) {} + + llvm::Optional getDeducedType() { return DeducedType; } + + // Handle auto initializers: + //- auto i = 1; + //- decltype(auto) i = 1; + //- auto& i = 1; + bool VisitDeclaratorDecl(DeclaratorDecl *D) { + if (!D->getTypeSourceInfo() || + D->getTypeSourceInfo()->getTypeLoc().getLocStart() != SearchedLocation) + return true; + + auto DeclT = D->getType(); + // "auto &" is represented as a ReferenceType containing an AutoType + if (const ReferenceType *RT = dyn_cast(DeclT.getTypePtr())) + DeclT = RT->getPointeeType(); + + const AutoType *AT = dyn_cast(DeclT.getTypePtr()); + if (AT && !AT->getDeducedType().isNull()) { + // For auto, use the underlying type because the const& would be + // represented twice: written in the code and in the hover. + // Example: "const auto I = 1", we only want "int" when hovering on auto, + // not "const int". + // + // For decltype(auto), take the type as is because it cannot be written + // with qualifiers or references but its decuded type can be const-ref. + DeducedType = AT->isDecltypeAuto() ? DeclT : DeclT.getUnqualifiedType(); + } + return true; + } + + // Handle auto return types: + //- auto foo() {} + //- auto& foo() {} + //- auto foo() -> decltype(1+1) {} + //- operator auto() const { return 10; } + bool VisitFunctionDecl(FunctionDecl *D) { + if (!D->getTypeSourceInfo()) + return true; + // Loc of auto in return type (c++14). + auto CurLoc = D->getReturnTypeSourceRange().getBegin(); + // Loc of "auto" in operator auto() + if (CurLoc.isInvalid() && dyn_cast(D)) + CurLoc = D->getTypeSourceInfo()->getTypeLoc().getBeginLoc(); + // Loc of "auto" in function with traling return type (c++11). + if (CurLoc.isInvalid()) + CurLoc = D->getSourceRange().getBegin(); + if (CurLoc != SearchedLocation) + return true; + + auto T = D->getReturnType(); + // "auto &" is represented as a ReferenceType containing an AutoType. + if (const ReferenceType *RT = dyn_cast(T.getTypePtr())) + T = RT->getPointeeType(); + + const AutoType *AT = dyn_cast(T.getTypePtr()); + if (AT && !AT->getDeducedType().isNull()) { + DeducedType = T.getUnqualifiedType(); + } else { // auto in a trailing return type just points to a DecltypeType. + const DecltypeType *DT = dyn_cast(T.getTypePtr()); + if (!DT->getUnderlyingType().isNull()) + DeducedType = DT->getUnderlyingType(); + } + return true; + } + + // Handle non-auto decltype, e.g.: + // - auto foo() -> decltype(expr) {} + // - decltype(expr); + bool VisitDecltypeTypeLoc(DecltypeTypeLoc TL) { + if (TL.getBeginLoc() != SearchedLocation) + return true; + + // A DecltypeType's underlying type can be another DecltypeType! E.g. + // int I = 0; + // decltype(I) J = I; + // decltype(J) K = J; + const DecltypeType *DT = dyn_cast(TL.getTypePtr()); + while (DT && !DT->getUnderlyingType().isNull()) { + DeducedType = DT->getUnderlyingType(); + DT = dyn_cast(DeducedType->getTypePtr()); + } + return true; + } +}; +} // namespace + +/// Retrieves the deduced type at a given location (auto, decltype). +llvm::Optional getDeducedType(ParsedAST &AST, + SourceLocation SourceLocationBeg) { + Token Tok; + auto &ASTCtx = AST.getASTContext(); + // Only try to find a deduced type if the token is auto or decltype. + if (!SourceLocationBeg.isValid() || + Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(), + ASTCtx.getLangOpts(), false) || + !Tok.is(tok::raw_identifier)) { + return {}; + } + AST.getPreprocessor().LookUpIdentifierInfo(Tok); + if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype))) + return {}; + + DeducedTypeVisitor V(SourceLocationBeg); + for (Decl *D : AST.getLocalTopLevelDecls()) + V.TraverseDecl(D); + return V.getDeducedType(); +} + +Optional getHover(ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + SourceLocation SourceLocationBeg = + getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + // Identified symbols at a specific position. + auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg); + + if (!Symbols.Macros.empty()) + return getHoverContents(Symbols.Macros[0].Name); + + if (!Symbols.Decls.empty()) + return getHoverContents(Symbols.Decls[0]); + + auto DeducedType = getDeducedType(AST, SourceLocationBeg); + if (DeducedType && !DeducedType->isNull()) + return getHoverContents(*DeducedType, AST.getASTContext()); + + return None; +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/XRefs.h b/clangd/XRefs.h new file mode 100644 index 000000000..d698a61c9 --- /dev/null +++ b/clangd/XRefs.h @@ -0,0 +1,38 @@ +//===--- XRefs.h ------------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Features that traverse references between symbols. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XREFS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XREFS_H + +#include "ClangdUnit.h" +#include "Protocol.h" +#include "index/Index.h" +#include "llvm/ADT/Optional.h" +#include + +namespace clang { +namespace clangd { + +/// Get definition of symbol at a specified \p Pos. +std::vector findDefinitions(ParsedAST &AST, Position Pos, + const SymbolIndex *Index = nullptr); + +/// Returns highlights for all usages of a symbol at \p Pos. +std::vector findDocumentHighlights(ParsedAST &AST, + Position Pos); + +/// Get the hover information when hovering at \p Pos. +llvm::Optional getHover(ParsedAST &AST, Position Pos); + +} // 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..1294fe265 --- /dev/null +++ b/clangd/clients/clangd-vscode/.gitignore @@ -0,0 +1,3 @@ +out +node_modules +package-lock.json 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/LICENSE b/clangd/clients/clangd-vscode/LICENSE new file mode 100644 index 000000000..c558158dd --- /dev/null +++ b/clangd/clients/clangd-vscode/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 The LLVM Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in 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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 +AUTHORS 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 IN THE +SOFTWARE. diff --git a/clangd/clients/clangd-vscode/README.md b/clangd/clients/clangd-vscode/README.md new file mode 100644 index 000000000..f950cb630 --- /dev/null +++ b/clangd/clients/clangd-vscode/README.md @@ -0,0 +1,76 @@ +# vscode-clangd + +Provides C/C++ language IDE features for VS Code using [clangd](https://clang.llvm.org/extra/clangd.html). + +## Usage + +`vscode-clangd` provides the features designated by the [Language Server +Protocol](https://github.com/Microsoft/language-server-protocol), such as +code completion, code formatting and goto definition. + +**Note**: `clangd` is under heavy development, not all LSP features are +implemented. See [Current Status](https://clang.llvm.org/extra/clangd.html#current-status) +for details. + +To use `vscode-clangd` extension in VS Code, you need to install `vscode-clangd` +from VS Code extension marketplace. + +`vscode-clangd` will attempt to find the `clangd` binary on your `PATH`. +Alternatively, the `clangd` executable can be specified in your VS Code +`settings.json` file: + +```json +{ + "clangd.path": "/absolute/path/to/clangd" +} +``` + +To obtain `clangd` binary, please see the [installing Clangd](https://clang.llvm.org/extra/clangd.html#installing-clangd). + +## Development + +A guide of developing `vscode-clangd` extension. + +### Requirements + +* VS Code +* node.js and npm + +### Steps + +1. Make sure you disable the installed `vscode-clangd` extension in VS Code. +2. Make sure you have clangd in /usr/bin/clangd or edit src/extension.ts to +point to the binary. +3. In order to start a development instance of VS code extended with this, run: + +```bash + $ cd /path/to/clang-tools-extra/clangd/clients/clangd-vscode/ + $ npm install + $ code . + # When VS Code starts, press . +``` + +## Publish to VS Code Marketplace + +New changes to `clangd-vscode` are not released until a new version is published +to the marketplace. + +### Requirements + +* Make sure install the `vsce` command (`npm install -g vsce`) +* `llvm-vs-code-extensions` account +* Bump the version in `package.json`, and commit the change to upstream + +The extension is published under `llvm-vs-code-extensions` account, which is +currently maintained by clangd developers. If you want to make a new release, +please contact cfe-dev@lists.llvm.org. + +### Steps + +```bash + $ cd /path/to/clang-tools-extra/clangd/clients/clangd-vscode/ + # For the first time, you need to login in the account. vsce will ask you for + the Personal Access Token, and remember it for future commands. + $ vsce login llvm-vs-code-extensions + $ vsce publish +``` diff --git a/clangd/clients/clangd-vscode/package.json b/clangd/clients/clangd-vscode/package.json new file mode 100644 index 000000000..eeb511288 --- /dev/null +++ b/clangd/clients/clangd-vscode/package.json @@ -0,0 +1,79 @@ +{ + "name": "vscode-clangd", + "displayName": "vscode-clangd", + "description": "Clang Language Server", + "version": "0.0.6", + "publisher": "llvm-vs-code-extensions", + "homepage": "https://clang.llvm.org/extra/clangd.html", + "engines": { + "vscode": "^1.18.0" + }, + "categories": [ + "Programming Languages", + "Linters", + "Snippets" + ], + "keywords": [ + "C", + "C++", + "LSP", + "Clangd", + "LLVM" + ], + "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": "^4.0.0", + "vscode-languageserver": "^4.0.0" + }, + "devDependencies": { + "typescript": "^2.0.3", + "vscode": "^1.1.0", + "mocha": "^2.3.3", + "@types/node": "^6.0.40", + "@types/mocha": "^2.2.32" + }, + "repository": { + "type": "svn", + "url": "http://llvm.org/svn/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/" + }, + "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" + }, + "clangd.syncFileEvents": { + "type": "boolean", + "default": true, + "description": "Whether or not to send file events to clangd (File created, changed or deleted). This can be disabled for performance consideration." + }, + "clangd.trace": { + "type": "string", + "description": "Names a file that clangd should log a performance trace to, in chrome trace-viewer JSON format." + } + } + } + } +} diff --git a/clangd/clients/clangd-vscode/src/extension.ts b/clangd/clients/clangd-vscode/src/extension.ts new file mode 100644 index 000000000..a126a19f9 --- /dev/null +++ b/clangd/clients/clangd-vscode/src/extension.ts @@ -0,0 +1,58 @@ +import * as vscode from 'vscode'; +import * as vscodelc from 'vscode-languageclient'; +import { realpathSync } from 'fs'; + +/** + * 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 syncFileEvents = getConfig('syncFileEvents', true); + + const clangd: vscodelc.Executable = { + command: getConfig('path'), + args: getConfig('arguments') + }; + const traceFile = getConfig('trace'); + if (!!traceFile) { + const trace = { CLANGD_TRACE: traceFile }; + clangd.options = { env: { ...process.env, ...trace } }; + } + const serverOptions: vscodelc.ServerOptions = clangd; + + const filePattern: string = '**/*.{' + + ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc'].join() + '}'; + const clientOptions: vscodelc.LanguageClientOptions = { + // Register the server for C/C++ files + documentSelector: [{ scheme: 'file', pattern: filePattern }], + synchronize: !syncFileEvents ? undefined : { + fileEvents: vscode.workspace.createFileSystemWatcher(filePattern) + }, + // Resolve symlinks for all files provided by clangd. + // This is a workaround for a bazel + clangd issue - bazel produces a symlink tree to build in, + // and when navigating to the included file, clangd passes its path inside the symlink tree + // rather than its filesystem path. + // FIXME: remove this once clangd knows enough about bazel to resolve the + // symlinks where needed (or if this causes problems for other workflows). + uriConverters: { + code2Protocol: (value: vscode.Uri) => value.toString(), + protocol2Code: (value: string) => + vscode.Uri.file(realpathSync(vscode.Uri.parse(value).fsPath)) + } + }; + + const clangdClient = new vscodelc.LanguageClient('Clang Language Server', serverOptions, clientOptions); + console.log('Clang Language Server is now active!'); + + const disposable = clangdClient.start(); +} 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/fuzzer/CMakeLists.txt b/clangd/fuzzer/CMakeLists.txt new file mode 100644 index 000000000..ca76c9774 --- /dev/null +++ b/clangd/fuzzer/CMakeLists.txt @@ -0,0 +1,24 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +set(LLVM_LINK_COMPONENTS support) + +if(LLVM_USE_SANITIZE_COVERAGE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=fuzzer") +endif() + +add_clang_executable(clangd-fuzzer + EXCLUDE_FROM_ALL + ClangdFuzzer.cpp + ) + +target_link_libraries(clangd-fuzzer + PRIVATE + clangBasic + clangDaemon + clangFormat + clangFrontend + clangSema + clangTooling + clangToolingCore + ${LLVM_LIB_FUZZING_ENGINE} + ) diff --git a/clangd/fuzzer/ClangdFuzzer.cpp b/clangd/fuzzer/ClangdFuzzer.cpp new file mode 100644 index 000000000..86f610707 --- /dev/null +++ b/clangd/fuzzer/ClangdFuzzer.cpp @@ -0,0 +1,37 @@ +//===-- ClangdFuzzer.cpp - Fuzz clangd ------------------------------------===// +// +// 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 implements a function that runs clangd on a single input. +/// This function is then linked into the Fuzzer library. +/// +//===----------------------------------------------------------------------===// + +#include "ClangdLSPServer.h" +#include "ClangdServer.h" +#include "CodeComplete.h" +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + if (size == 0) + return 0; + + clang::clangd::JSONOutput Out(llvm::nulls(), llvm::nulls(), + clang::clangd::Logger::Error, nullptr); + clang::clangd::CodeCompleteOptions CCOpts; + CCOpts.EnableSnippets = false; + clang::clangd::ClangdServer::Options Opts; + + // Initialize and run ClangdLSPServer. + clang::clangd::ClangdLSPServer LSPServer(Out, CCOpts, llvm::None, Opts); + // fmemopen isn't portable, but I think we only run the fuzzer on Linux. + LSPServer.run(fmemopen(data, size, "r")); + return 0; +} diff --git a/clangd/global-symbol-builder/CMakeLists.txt b/clangd/global-symbol-builder/CMakeLists.txt new file mode 100644 index 000000000..c81da16ed --- /dev/null +++ b/clangd/global-symbol-builder/CMakeLists.txt @@ -0,0 +1,20 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) + +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(global-symbol-builder + GlobalSymbolBuilderMain.cpp + ) + +target_link_libraries(global-symbol-builder + PRIVATE + clangAST + clangIndex + clangDaemon + clangBasic + clangFrontend + clangLex + clangTooling +) diff --git a/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp b/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp new file mode 100644 index 000000000..0cc048021 --- /dev/null +++ b/clangd/global-symbol-builder/GlobalSymbolBuilderMain.cpp @@ -0,0 +1,199 @@ +//===--- GlobalSymbolBuilderMain.cpp -----------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// GlobalSymbolBuilder is a tool to generate YAML-format symbols across the +// whole project. This tools is for **experimental** only. Don't use it in +// production code. +// +//===---------------------------------------------------------------------===// + +#include "index/CanonicalIncludes.h" +#include "index/Index.h" +#include "index/Merge.h" +#include "index/SymbolCollector.h" +#include "index/SymbolYAML.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/YAMLTraits.h" + +using namespace llvm; +using namespace clang::tooling; +using clang::clangd::SymbolSlab; + +namespace clang { +namespace clangd { +namespace { + +static llvm::cl::opt AssumedHeaderDir( + "assume-header-dir", + llvm::cl::desc("The index includes header that a symbol is defined in. " + "If the absolute path cannot be determined (e.g. an " + "in-memory VFS) then the relative path is resolved against " + "this directory, which must be absolute. If this flag is " + "not given, such headers will have relative paths."), + llvm::cl::init("")); + +class SymbolIndexActionFactory : public tooling::FrontendActionFactory { +public: + SymbolIndexActionFactory(tooling::ExecutionContext *Ctx) : Ctx(Ctx) {} + + clang::FrontendAction *create() override { + // Wraps the index action and reports collected symbols to the execution + // context at the end of each translation unit. + class WrappedIndexAction : public WrapperFrontendAction { + public: + WrappedIndexAction(std::shared_ptr C, + std::unique_ptr Includes, + const index::IndexingOptions &Opts, + tooling::ExecutionContext *Ctx) + : WrapperFrontendAction( + index::createIndexingAction(C, Opts, nullptr)), + Ctx(Ctx), Collector(C), Includes(std::move(Includes)), + PragmaHandler(collectIWYUHeaderMaps(this->Includes.get())) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { + CI.getPreprocessor().addCommentHandler(PragmaHandler.get()); + return WrapperFrontendAction::CreateASTConsumer(CI, InFile); + } + + bool BeginInvocation(CompilerInstance &CI) override { + // We want all comments, not just the doxygen ones. + CI.getLangOpts().CommentOpts.ParseAllComments = true; + return WrapperFrontendAction::BeginInvocation(CI); + } + + void EndSourceFileAction() override { + WrapperFrontendAction::EndSourceFileAction(); + + const auto &CI = getCompilerInstance(); + if (CI.hasDiagnostics() && + CI.getDiagnostics().hasUncompilableErrorOccurred()) { + llvm::errs() + << "Found uncompilable errors in the translation unit. Igoring " + "collected symbols...\n"; + return; + } + + auto Symbols = Collector->takeSymbols(); + for (const auto &Sym : Symbols) { + Ctx->reportResult(Sym.ID.str(), SymbolToYAML(Sym)); + } + } + + private: + tooling::ExecutionContext *Ctx; + std::shared_ptr Collector; + std::unique_ptr Includes; + std::unique_ptr PragmaHandler; + }; + + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = false; + auto CollectorOpts = SymbolCollector::Options(); + CollectorOpts.FallbackDir = AssumedHeaderDir; + CollectorOpts.CollectIncludePath = true; + CollectorOpts.CountReferences = true; + CollectorOpts.Origin = SymbolOrigin::Static; + auto Includes = llvm::make_unique(); + addSystemHeadersMapping(Includes.get()); + CollectorOpts.Includes = Includes.get(); + return new WrappedIndexAction( + std::make_shared(std::move(CollectorOpts)), + std::move(Includes), IndexOpts, Ctx); + } + + tooling::ExecutionContext *Ctx; +}; + +// Combine occurrences of the same symbol across translation units. +SymbolSlab mergeSymbols(tooling::ToolResults *Results) { + SymbolSlab::Builder UniqueSymbols; + llvm::BumpPtrAllocator Arena; + Symbol::Details Scratch; + Results->forEachResult([&](llvm::StringRef Key, llvm::StringRef Value) { + Arena.Reset(); + llvm::yaml::Input Yin(Value, &Arena); + auto Sym = clang::clangd::SymbolFromYAML(Yin, Arena); + clang::clangd::SymbolID ID; + Key >> ID; + if (const auto *Existing = UniqueSymbols.find(ID)) + UniqueSymbols.insert(mergeSymbol(*Existing, Sym, &Scratch)); + else + UniqueSymbols.insert(Sym); + }); + return std::move(UniqueSymbols).build(); +} + +} // namespace +} // namespace clangd +} // namespace clang + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + const char *Overview = R"( + This is an **experimental** tool to generate YAML-format project-wide symbols + for clangd (global code completion). It would be changed and deprecated + eventually. Don't use it in production code! + + Example usage for building index for the whole project using CMake compile + commands: + + $ global-symbol-builder --executor=all-TUs compile_commands.json > index.yaml + + Example usage for file sequence index without flags: + + $ global-symbol-builder File1.cpp File2.cpp ... FileN.cpp > index.yaml + + Note: only symbols from header files will be collected. + )"; + + auto Executor = clang::tooling::createExecutorFromCommandLineArgs( + argc, argv, cl::GeneralCategory, Overview); + + if (!Executor) { + llvm::errs() << llvm::toString(Executor.takeError()) << "\n"; + return 1; + } + + if (!clang::clangd::AssumedHeaderDir.empty() && + !llvm::sys::path::is_absolute(clang::clangd::AssumedHeaderDir)) { + llvm::errs() << "--assume-header-dir must be an absolute path.\n"; + return 1; + } + + // Map phase: emit symbols found in each translation unit. + auto Err = Executor->get()->execute( + llvm::make_unique( + Executor->get()->getExecutionContext())); + if (Err) { + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + } + + // Reduce phase: combine symbols using the ID as a key. + auto UniqueSymbols = + clang::clangd::mergeSymbols(Executor->get()->getToolResults()); + + // Output phase: emit YAML for result symbols. + SymbolsToYAML(UniqueSymbols, llvm::outs()); + return 0; +} diff --git a/clangd/index/CanonicalIncludes.cpp b/clangd/index/CanonicalIncludes.cpp new file mode 100644 index 000000000..a3493e316 --- /dev/null +++ b/clangd/index/CanonicalIncludes.cpp @@ -0,0 +1,813 @@ +//===-- CanonicalIncludes.h - remap #inclue headers--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CanonicalIncludes.h" +#include "../Headers.h" +#include "clang/Driver/Types.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace clangd { +namespace { +const char IWYUPragma[] = "// IWYU pragma: private, include "; +} // namespace + +void CanonicalIncludes::addMapping(llvm::StringRef Path, + llvm::StringRef CanonicalPath) { + addRegexMapping((llvm::Twine("^") + llvm::Regex::escape(Path) + "$").str(), + CanonicalPath); +} + +void CanonicalIncludes::addRegexMapping(llvm::StringRef RE, + llvm::StringRef CanonicalPath) { + this->RegexHeaderMappingTable.emplace_back(llvm::Regex(RE), CanonicalPath); +} + +void CanonicalIncludes::addSymbolMapping(llvm::StringRef QualifiedName, + llvm::StringRef CanonicalPath) { + this->SymbolMapping[QualifiedName] = CanonicalPath; +} + +llvm::StringRef +CanonicalIncludes::mapHeader(llvm::ArrayRef Headers, + llvm::StringRef QualifiedName) const { + assert(!Headers.empty()); + auto SE = SymbolMapping.find(QualifiedName); + if (SE != SymbolMapping.end()) + return SE->second; + std::lock_guard Lock(RegexMutex); + // Find the first header such that the extension is not '.inc', and isn't a + // recognized non-header file + auto I = + std::find_if(Headers.begin(), Headers.end(), [](llvm::StringRef Include) { + // Skip .inc file whose including header file should + // be #included instead. + return !Include.endswith(".inc"); + }); + if (I == Headers.end()) + return Headers[0]; // Fallback to the declaring header. + StringRef Header = *I; + // If Header is not expected be included (e.g. .cc file), we fall back to + // the declaring header. + StringRef Ext = llvm::sys::path::extension(Header).trim('.'); + // Include-able headers must have precompile type. Treat files with + // non-recognized extenstions (TY_INVALID) as headers. + auto ExtType = driver::types::lookupTypeForExtension(Ext); + if ((ExtType != driver::types::TY_INVALID) && + !driver::types::onlyPrecompileType(ExtType)) + return Headers[0]; + for (auto &Entry : RegexHeaderMappingTable) { +#ifndef NDEBUG + std::string Dummy; + assert(Entry.first.isValid(Dummy) && "Regex should never be invalid!"); +#endif + if (Entry.first.match(Header)) + return Entry.second; + } + return Header; +} + +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes) { + class PragmaCommentHandler : public clang::CommentHandler { + public: + PragmaCommentHandler(CanonicalIncludes *Includes) : Includes(Includes) {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + if (!Text.consume_front(IWYUPragma)) + return false; + // FIXME(ioeric): resolve the header and store actual file path. For now, + // we simply assume the written header is suitable to be #included. + Includes->addMapping(PP.getSourceManager().getFilename(Range.getBegin()), + isLiteralInclude(Text) ? Text.str() + : ("\"" + Text + "\"").str()); + return false; + } + + private: + CanonicalIncludes *const Includes; + }; + return llvm::make_unique(Includes); +} + +void addSystemHeadersMapping(CanonicalIncludes *Includes) { + static const std::vector> SymbolMap = { + {"std::addressof", ""}, + // Map symbols in to their preferred includes. + {"std::basic_filebuf", ""}, + {"std::basic_fstream", ""}, + {"std::basic_ifstream", ""}, + {"std::basic_ofstream", ""}, + {"std::filebuf", ""}, + {"std::fstream", ""}, + {"std::ifstream", ""}, + {"std::ofstream", ""}, + {"std::wfilebuf", ""}, + {"std::wfstream", ""}, + {"std::wifstream", ""}, + {"std::wofstream", ""}, + {"std::basic_ios", ""}, + {"std::ios", ""}, + {"std::wios", ""}, + {"std::basic_iostream", ""}, + {"std::iostream", ""}, + {"std::wiostream", ""}, + {"std::basic_istream", ""}, + {"std::istream", ""}, + {"std::wistream", ""}, + {"std::istreambuf_iterator", ""}, + {"std::ostreambuf_iterator", ""}, + {"std::basic_ostream", ""}, + {"std::ostream", ""}, + {"std::wostream", ""}, + {"std::basic_istringstream", ""}, + {"std::basic_ostringstream", ""}, + {"std::basic_stringbuf", ""}, + {"std::basic_stringstream", ""}, + {"std::istringstream", ""}, + {"std::ostringstream", ""}, + {"std::stringbuf", ""}, + {"std::stringstream", ""}, + {"std::wistringstream", ""}, + {"std::wostringstream", ""}, + {"std::wstringbuf", ""}, + {"std::wstringstream", ""}, + {"std::basic_streambuf", ""}, + {"std::streambuf", ""}, + {"std::wstreambuf", ""}, + {"std::uint_least16_t", ""}, // redeclares these + {"std::uint_least32_t", ""}, + {"std::declval", ""}, + }; + for (const auto &Pair : SymbolMap) + Includes->addSymbolMapping(Pair.first, Pair.second); + + static const std::vector> + SystemHeaderMap = { + {"include/__stddef_max_align_t.h$", ""}, + {"include/__wmmintrin_aes.h$", ""}, + {"include/__wmmintrin_pclmul.h$", ""}, + {"include/adxintrin.h$", ""}, + {"include/ammintrin.h$", ""}, + {"include/avx2intrin.h$", ""}, + {"include/avx512bwintrin.h$", ""}, + {"include/avx512cdintrin.h$", ""}, + {"include/avx512dqintrin.h$", ""}, + {"include/avx512erintrin.h$", ""}, + {"include/avx512fintrin.h$", ""}, + {"include/avx512ifmaintrin.h$", ""}, + {"include/avx512ifmavlintrin.h$", ""}, + {"include/avx512pfintrin.h$", ""}, + {"include/avx512vbmiintrin.h$", ""}, + {"include/avx512vbmivlintrin.h$", ""}, + {"include/avx512vlbwintrin.h$", ""}, + {"include/avx512vlcdintrin.h$", ""}, + {"include/avx512vldqintrin.h$", ""}, + {"include/avx512vlintrin.h$", ""}, + {"include/avxintrin.h$", ""}, + {"include/bmi2intrin.h$", ""}, + {"include/bmiintrin.h$", ""}, + {"include/emmintrin.h$", ""}, + {"include/f16cintrin.h$", ""}, + {"include/float.h$", ""}, + {"include/fma4intrin.h$", ""}, + {"include/fmaintrin.h$", ""}, + {"include/fxsrintrin.h$", ""}, + {"include/ia32intrin.h$", ""}, + {"include/immintrin.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/limits.h$", ""}, + {"include/lzcntintrin.h$", ""}, + {"include/mm3dnow.h$", ""}, + {"include/mm_malloc.h$", ""}, + {"include/mmintrin.h$", ""}, + {"include/mwaitxintrin.h$", ""}, + {"include/pkuintrin.h$", ""}, + {"include/pmmintrin.h$", ""}, + {"include/popcntintrin.h$", ""}, + {"include/prfchwintrin.h$", ""}, + {"include/rdseedintrin.h$", ""}, + {"include/rtmintrin.h$", ""}, + {"include/shaintrin.h$", ""}, + {"include/smmintrin.h$", ""}, + {"include/stdalign.h$", ""}, + {"include/stdarg.h$", ""}, + {"include/stdbool.h$", ""}, + {"include/stddef.h$", ""}, + {"include/stdint.h$", ""}, + {"include/tbmintrin.h$", ""}, + {"include/tmmintrin.h$", ""}, + {"include/wmmintrin.h$", ""}, + {"include/x86intrin.h$", ""}, + {"include/xmmintrin.h$", ""}, + {"include/xopintrin.h$", ""}, + {"include/xsavecintrin.h$", ""}, + {"include/xsaveintrin.h$", ""}, + {"include/xsaveoptintrin.h$", ""}, + {"include/xsavesintrin.h$", ""}, + {"include/xtestintrin.h$", ""}, + {"include/_G_config.h$", ""}, + {"include/assert.h$", ""}, + {"algorithm$", ""}, + {"valarray$", ""}, + {"array$", ""}, + {"atomic$", ""}, + {"backward/auto_ptr.h$", ""}, + {"backward/binders.h$", ""}, + {"bits/algorithmfwd.h$", ""}, + {"bits/alloc_traits.h$", ""}, + {"bits/allocated_ptr.h$", ""}, + {"bits/allocator.h$", ""}, + {"bits/atomic_base.h$", ""}, + {"bits/atomic_lockfree_defines.h$", ""}, + {"bits/atomic_futex.h$", ""}, + {"bits/basic_ios.h$", ""}, + {"bits/basic_ios.tcc$", ""}, + {"bits/basic_string.h$", ""}, + {"bits/basic_string.tcc$", ""}, + {"bits/char_traits.h$", ""}, + {"bits/codecvt.h$", ""}, + {"bits/concept_check.h$", ""}, + {"bits/cpp_type_traits.h$", ""}, + {"bits/cxxabi_forced.h$", ""}, + {"bits/deque.tcc$", ""}, + {"bits/exception.h$", ""}, + {"bits/exception_defines.h$", ""}, + {"bits/exception_ptr.h$", ""}, + {"bits/forward_list.h$", ""}, + {"bits/forward_list.tcc$", ""}, + {"bits/fstream.tcc$", ""}, + {"bits/functexcept.h$", ""}, + {"bits/functional_hash.h$", ""}, + {"bits/gslice.h$", ""}, + {"bits/gslice_array.h$", ""}, + {"bits/hash_bytes.h$", ""}, + {"bits/hashtable.h$", ""}, + {"bits/hashtable_policy.h$", ""}, + {"bits/indirect_array.h$", ""}, + {"bits/invoke.h$", ""}, + {"bits/ios_base.h$", ""}, + {"bits/istream.tcc$", ""}, + {"bits/list.tcc$", ""}, + {"bits/locale_classes.h$", ""}, + {"bits/locale_classes.tcc$", ""}, + {"bits/locale_conv.h$", ""}, + {"bits/locale_facets.h$", ""}, + {"bits/locale_facets.tcc$", ""}, + {"bits/locale_facets_nonio.h$", ""}, + {"bits/locale_facets_nonio.tcc$", ""}, + {"bits/localefwd.h$", ""}, + {"bits/mask_array.h$", ""}, + {"bits/memoryfwd.h$", ""}, + {"bits/move.h$", ""}, + {"bits/nested_exception.h$", ""}, + {"bits/ostream.tcc$", ""}, + {"bits/ostream_insert.h$", ""}, + {"bits/parse_numbers.h$", ""}, + {"bits/postypes.h$", ""}, + {"bits/predefined_ops.h$", ""}, + {"bits/ptr_traits.h$", ""}, + {"bits/quoted_string.h$", ""}, + {"bits/random.h$", ""}, + {"bits/random.tcc$", ""}, + {"bits/range_access.h$", ""}, + {"bits/refwrap.h$", ""}, + {"bits/regex.h$", ""}, + {"bits/regex_automaton.h$", ""}, + {"bits/regex_compiler.h$", ""}, + {"bits/regex_constants.h$", ""}, + {"bits/regex_cursor.h$", ""}, + {"bits/regex_error.h$", ""}, + {"bits/regex_executor.h$", ""}, + {"bits/regex_grep_matcher.h$", ""}, + {"bits/regex_grep_matcher.tcc$", ""}, + {"bits/regex_nfa.h$", ""}, + {"bits/regex_scanner.h$", ""}, + {"bits/shared_ptr.h$", ""}, + {"bits/shared_ptr_base.h$", ""}, + {"bits/shared_ptr_atomic.h$", ""}, + {"bits/slice_array.h$", ""}, + {"bits/sstream.tcc$", ""}, + {"bits/std_abs.h$", ""}, + {"bits/std_function.h$", ""}, + {"bits/std_mutex.h$", ""}, + {"bits/stl_algo.h$", ""}, + {"bits/stl_algobase.h$", ""}, + {"bits/stl_bvector.h$", ""}, + {"bits/stl_construct.h$", ""}, + {"bits/stl_deque.h$", ""}, + {"bits/stl_function.h$", ""}, + {"bits/stl_heap.h$", ""}, + {"bits/stl_iterator.h$", ""}, + {"bits/stl_iterator_base_funcs.h$", ""}, + {"bits/stl_iterator_base_types.h$", ""}, + {"bits/stl_list.h$", ""}, + {"bits/stl_map.h$", ""}, + {"bits/stl_multimap.h$", ""}, + {"bits/stl_multiset.h$", ""}, + {"bits/stl_numeric.h$", ""}, + {"bits/stl_pair.h$", ""}, + {"bits/stl_queue.h$", ""}, + {"bits/stl_raw_storage_iter.h$", ""}, + {"bits/stl_relops.h$", ""}, + {"bits/stl_set.h$", ""}, + {"bits/stl_stack.h$", ""}, + {"bits/stl_tempbuf.h$", ""}, + {"bits/stl_tree.h$", ""}, + {"bits/stl_uninitialized.h$", ""}, + {"bits/stl_vector.h$", ""}, + {"bits/stream_iterator.h$", ""}, + {"bits/streambuf.tcc$", ""}, + {"bits/streambuf_iterator.h$", ""}, + {"bits/stringfwd.h$", ""}, + {"bits/uniform_int_dist.h$", ""}, + {"bits/unique_ptr.h$", ""}, + {"bits/unordered_map.h$", ""}, + {"bits/unordered_set.h$", ""}, + {"bits/uses_allocator.h$", ""}, + {"bits/valarray_after.h$", ""}, + {"bits/valarray_array.h$", ""}, + {"bits/valarray_array.tcc$", ""}, + {"bits/valarray_before.h$", ""}, + {"bits/vector.tcc$", ""}, + {"bitset$", ""}, + {"ccomplex$", ""}, + {"cctype$", ""}, + {"cerrno$", ""}, + {"cfenv$", ""}, + {"cfloat$", ""}, + {"chrono$", ""}, + {"cinttypes$", ""}, + {"climits$", ""}, + {"clocale$", ""}, + {"cmath$", ""}, + {"complex$", ""}, + {"complex.h$", ""}, + {"condition_variable$", ""}, + {"csetjmp$", ""}, + {"csignal$", ""}, + {"cstdalign$", ""}, + {"cstdarg$", ""}, + {"cstdbool$", ""}, + {"cstdint$", ""}, + {"cstdio$", ""}, + {"cstdlib$", ""}, + {"cstring$", ""}, + {"ctgmath$", ""}, + {"ctime$", ""}, + {"cwchar$", ""}, + {"cwctype$", ""}, + {"cxxabi.h$", ""}, + {"debug/debug.h$", ""}, + {"debug/map.h$", ""}, + {"debug/multimap.h$", ""}, + {"debug/multiset.h$", ""}, + {"debug/set.h$", ""}, + {"deque$", ""}, + {"exception$", ""}, + {"ext/alloc_traits.h$", ""}, + {"ext/atomicity.h$", ""}, + {"ext/concurrence.h$", ""}, + {"ext/new_allocator.h$", ""}, + {"ext/numeric_traits.h$", ""}, + {"ext/string_conversions.h$", ""}, + {"ext/type_traits.h$", ""}, + {"fenv.h$", ""}, + {"forward_list$", ""}, + {"fstream$", ""}, + {"functional$", ""}, + {"future$", ""}, + {"initializer_list$", ""}, + {"iomanip$", ""}, + {"ios$", ""}, + {"iosfwd$", ""}, + {"iostream$", ""}, + {"istream$", ""}, + {"iterator$", ""}, + {"limits$", ""}, + {"list$", ""}, + {"locale$", ""}, + {"map$", ""}, + {"memory$", ""}, + {"shared_mutex$", ""}, + {"mutex$", ""}, + {"new$", ""}, + {"numeric$", ""}, + {"ostream$", ""}, + {"queue$", ""}, + {"random$", ""}, + {"ratio$", ""}, + {"regex$", ""}, + {"scoped_allocator$", ""}, + {"set$", ""}, + {"sstream$", ""}, + {"stack$", ""}, + {"stdexcept$", ""}, + {"streambuf$", ""}, + {"string$", ""}, + {"system_error$", ""}, + {"tgmath.h$", ""}, + {"thread$", ""}, + {"tuple$", ""}, + {"type_traits$", ""}, + {"typeindex$", ""}, + {"typeinfo$", ""}, + {"unordered_map$", ""}, + {"unordered_set$", ""}, + {"utility$", ""}, + {"valarray$", ""}, + {"vector$", ""}, + {"include/complex.h$", ""}, + {"include/ctype.h$", ""}, + {"include/errno.h$", ""}, + {"include/fenv.h$", ""}, + {"include/inttypes.h$", ""}, + {"include/libio.h$", ""}, + {"include/limits.h$", ""}, + {"include/locale.h$", ""}, + {"include/math.h$", ""}, + {"include/setjmp.h$", ""}, + {"include/signal.h$", ""}, + {"include/stdint.h$", ""}, + {"include/stdio.h$", ""}, + {"include/stdlib.h$", ""}, + {"include/string.h$", ""}, + {"include/time.h$", ""}, + {"include/wchar.h$", ""}, + {"include/wctype.h$", ""}, + {"bits/cmathcalls.h$", ""}, + {"bits/errno.h$", ""}, + {"bits/fenv.h$", ""}, + {"bits/huge_val.h$", ""}, + {"bits/huge_valf.h$", ""}, + {"bits/huge_vall.h$", ""}, + {"bits/inf.h$", ""}, + {"bits/local_lim.h$", ""}, + {"bits/locale.h$", ""}, + {"bits/mathcalls.h$", ""}, + {"bits/mathdef.h$", ""}, + {"bits/nan.h$", ""}, + {"bits/posix1_lim.h$", ""}, + {"bits/posix2_lim.h$", ""}, + {"bits/setjmp.h$", ""}, + {"bits/sigaction.h$", ""}, + {"bits/sigcontext.h$", ""}, + {"bits/siginfo.h$", ""}, + {"bits/signum.h$", ""}, + {"bits/sigset.h$", ""}, + {"bits/sigstack.h$", ""}, + {"bits/stdio_lim.h$", ""}, + {"bits/sys_errlist.h$", ""}, + {"bits/time.h$", ""}, + {"bits/timex.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/wchar.h$", ""}, + {"bits/wordsize.h$", ""}, + {"bits/xopen_lim.h$", ""}, + {"include/xlocale.h$", ""}, + {"bits/atomic_word.h$", ""}, + {"bits/basic_file.h$", ""}, + {"bits/c\\+\\+allocator.h$", ""}, + {"bits/c\\+\\+config.h$", ""}, + {"bits/c\\+\\+io.h$", ""}, + {"bits/c\\+\\+locale.h$", ""}, + {"bits/cpu_defines.h$", ""}, + {"bits/ctype_base.h$", ""}, + {"bits/cxxabi_tweaks.h$", ""}, + {"bits/error_constants.h$", ""}, + {"bits/gthr-default.h$", ""}, + {"bits/gthr.h$", ""}, + {"bits/opt_random.h$", ""}, + {"bits/os_defines.h$", ""}, + // GNU C headers + {"include/aio.h$", ""}, + {"include/aliases.h$", ""}, + {"include/alloca.h$", ""}, + {"include/ar.h$", ""}, + {"include/argp.h$", ""}, + {"include/argz.h$", ""}, + {"include/arpa/nameser.h$", ""}, + {"include/arpa/nameser_compat.h$", ""}, + {"include/byteswap.h$", ""}, + {"include/cpio.h$", ""}, + {"include/crypt.h$", ""}, + {"include/dirent.h$", ""}, + {"include/dlfcn.h$", ""}, + {"include/elf.h$", ""}, + {"include/endian.h$", ""}, + {"include/envz.h$", ""}, + {"include/err.h$", ""}, + {"include/error.h$", ""}, + {"include/execinfo.h$", ""}, + {"include/fcntl.h$", ""}, + {"include/features.h$", ""}, + {"include/fenv.h$", ""}, + {"include/fmtmsg.h$", ""}, + {"include/fnmatch.h$", ""}, + {"include/fstab.h$", ""}, + {"include/fts.h$", ""}, + {"include/ftw.h$", ""}, + {"include/gconv.h$", ""}, + {"include/getopt.h$", ""}, + {"include/glob.h$", ""}, + {"include/grp.h$", ""}, + {"include/gshadow.h$", ""}, + {"include/iconv.h$", ""}, + {"include/ifaddrs.h$", ""}, + {"include/kdb.h$", ""}, + {"include/langinfo.h$", ""}, + {"include/libgen.h$", ""}, + {"include/libintl.h$", ""}, + {"include/link.h$", ""}, + {"include/malloc.h$", ""}, + {"include/mcheck.h$", ""}, + {"include/memory.h$", ""}, + {"include/mntent.h$", ""}, + {"include/monetary.h$", ""}, + {"include/mqueue.h$", ""}, + {"include/netdb.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/nl_types.h$", ""}, + {"include/nss.h$", ""}, + {"include/obstack.h$", ""}, + {"include/panel.h$", ""}, + {"include/paths.h$", ""}, + {"include/printf.h$", ""}, + {"include/profile.h$", ""}, + {"include/pthread.h$", ""}, + {"include/pty.h$", ""}, + {"include/pwd.h$", ""}, + {"include/re_comp.h$", ""}, + {"include/regex.h$", ""}, + {"include/regexp.h$", ""}, + {"include/resolv.h$", ""}, + {"include/rpc/netdb.h$", ""}, + {"include/sched.h$", ""}, + {"include/search.h$", ""}, + {"include/semaphore.h$", ""}, + {"include/sgtty.h$", ""}, + {"include/shadow.h$", ""}, + {"include/spawn.h$", ""}, + {"include/stab.h$", ""}, + {"include/stdc-predef.h$", ""}, + {"include/stdio_ext.h$", ""}, + {"include/strings.h$", ""}, + {"include/stropts.h$", ""}, + {"include/sudo_plugin.h$", ""}, + {"include/sysexits.h$", ""}, + {"include/tar.h$", ""}, + {"include/tcpd.h$", ""}, + {"include/term.h$", ""}, + {"include/term_entry.h$", ""}, + {"include/termcap.h$", ""}, + {"include/termios.h$", ""}, + {"include/thread_db.h$", ""}, + {"include/tic.h$", ""}, + {"include/ttyent.h$", ""}, + {"include/uchar.h$", ""}, + {"include/ucontext.h$", ""}, + {"include/ulimit.h$", ""}, + {"include/unctrl.h$", ""}, + {"include/unistd.h$", ""}, + {"include/utime.h$", ""}, + {"include/utmp.h$", ""}, + {"include/utmpx.h$", ""}, + {"include/values.h$", ""}, + {"include/wordexp.h$", ""}, + {"fpu_control.h$", ""}, + {"ieee754.h$", ""}, + {"include/xlocale.h$", ""}, + {"gnu/lib-names.h$", ""}, + {"gnu/libc-version.h$", ""}, + {"gnu/option-groups.h$", ""}, + {"gnu/stubs-32.h$", ""}, + {"gnu/stubs-64.h$", ""}, + {"gnu/stubs-x32.h$", ""}, + {"include/rpc/auth_des.h$", ""}, + {"include/rpc/rpc_msg.h$", ""}, + {"include/rpc/pmap_clnt.h$", ""}, + {"include/rpc/rpc.h$", ""}, + {"include/rpc/types.h$", ""}, + {"include/rpc/auth_unix.h$", ""}, + {"include/rpc/key_prot.h$", ""}, + {"include/rpc/pmap_prot.h$", ""}, + {"include/rpc/auth.h$", ""}, + {"include/rpc/svc_auth.h$", ""}, + {"include/rpc/xdr.h$", ""}, + {"include/rpc/pmap_rmt.h$", ""}, + {"include/rpc/des_crypt.h$", ""}, + {"include/rpc/svc.h$", ""}, + {"include/rpc/rpc_des.h$", ""}, + {"include/rpc/clnt.h$", ""}, + {"include/scsi/scsi.h$", ""}, + {"include/scsi/sg.h$", ""}, + {"include/scsi/scsi_ioctl.h$", ""}, + {"include/netrose/rose.h$", ""}, + {"include/nfs/nfs.h$", ""}, + {"include/netatalk/at.h$", ""}, + {"include/netinet/ether.h$", ""}, + {"include/netinet/icmp6.h$", ""}, + {"include/netinet/if_ether.h$", ""}, + {"include/netinet/if_fddi.h$", ""}, + {"include/netinet/if_tr.h$", ""}, + {"include/netinet/igmp.h$", ""}, + {"include/netinet/in.h$", ""}, + {"include/netinet/in_systm.h$", ""}, + {"include/netinet/ip.h$", ""}, + {"include/netinet/ip6.h$", ""}, + {"include/netinet/ip_icmp.h$", ""}, + {"include/netinet/tcp.h$", ""}, + {"include/netinet/udp.h$", ""}, + {"include/netrom/netrom.h$", ""}, + {"include/protocols/routed.h$", ""}, + {"include/protocols/rwhod.h$", ""}, + {"include/protocols/talkd.h$", ""}, + {"include/protocols/timed.h$", ""}, + {"include/rpcsvc/klm_prot.x$", ""}, + {"include/rpcsvc/rstat.h$", ""}, + {"include/rpcsvc/spray.x$", ""}, + {"include/rpcsvc/nlm_prot.x$", ""}, + {"include/rpcsvc/nis_callback.x$", ""}, + {"include/rpcsvc/yp.h$", ""}, + {"include/rpcsvc/yp.x$", ""}, + {"include/rpcsvc/nfs_prot.h$", ""}, + {"include/rpcsvc/rex.h$", ""}, + {"include/rpcsvc/yppasswd.h$", ""}, + {"include/rpcsvc/rex.x$", ""}, + {"include/rpcsvc/nis_tags.h$", ""}, + {"include/rpcsvc/nis_callback.h$", ""}, + {"include/rpcsvc/nfs_prot.x$", ""}, + {"include/rpcsvc/bootparam_prot.x$", ""}, + {"include/rpcsvc/rusers.x$", ""}, + {"include/rpcsvc/rquota.x$", ""}, + {"include/rpcsvc/nis.h$", ""}, + {"include/rpcsvc/nislib.h$", ""}, + {"include/rpcsvc/ypupd.h$", ""}, + {"include/rpcsvc/bootparam.h$", ""}, + {"include/rpcsvc/spray.h$", ""}, + {"include/rpcsvc/key_prot.h$", ""}, + {"include/rpcsvc/klm_prot.h$", ""}, + {"include/rpcsvc/sm_inter.h$", ""}, + {"include/rpcsvc/nlm_prot.h$", ""}, + {"include/rpcsvc/yp_prot.h$", ""}, + {"include/rpcsvc/ypclnt.h$", ""}, + {"include/rpcsvc/rstat.x$", ""}, + {"include/rpcsvc/rusers.h$", ""}, + {"include/rpcsvc/key_prot.x$", ""}, + {"include/rpcsvc/sm_inter.x$", ""}, + {"include/rpcsvc/rquota.h$", ""}, + {"include/rpcsvc/nis.x$", ""}, + {"include/rpcsvc/bootparam_prot.h$", ""}, + {"include/rpcsvc/mount.h$", ""}, + {"include/rpcsvc/mount.x$", ""}, + {"include/rpcsvc/nis_object.x$", ""}, + {"include/rpcsvc/yppasswd.x$", ""}, + {"sys/acct.h$", ""}, + {"sys/auxv.h$", ""}, + {"sys/cdefs.h$", ""}, + {"sys/debugreg.h$", ""}, + {"sys/dir.h$", ""}, + {"sys/elf.h$", ""}, + {"sys/epoll.h$", ""}, + {"sys/eventfd.h$", ""}, + {"sys/fanotify.h$", ""}, + {"sys/file.h$", ""}, + {"sys/fsuid.h$", ""}, + {"sys/gmon.h$", ""}, + {"sys/gmon_out.h$", ""}, + {"sys/inotify.h$", ""}, + {"sys/io.h$", ""}, + {"sys/ioctl.h$", ""}, + {"sys/ipc.h$", ""}, + {"sys/kd.h$", ""}, + {"sys/kdaemon.h$", ""}, + {"sys/klog.h$", ""}, + {"sys/mman.h$", ""}, + {"sys/mount.h$", ""}, + {"sys/msg.h$", ""}, + {"sys/mtio.h$", ""}, + {"sys/param.h$", ""}, + {"sys/pci.h$", ""}, + {"sys/perm.h$", ""}, + {"sys/personality.h$", ""}, + {"sys/poll.h$", ""}, + {"sys/prctl.h$", ""}, + {"sys/procfs.h$", ""}, + {"sys/profil.h$", ""}, + {"sys/ptrace.h$", ""}, + {"sys/queue.h$", ""}, + {"sys/quota.h$", ""}, + {"sys/raw.h$", ""}, + {"sys/reboot.h$", ""}, + {"sys/reg.h$", ""}, + {"sys/resource.h$", ""}, + {"sys/select.h$", ""}, + {"sys/sem.h$", ""}, + {"sys/sendfile.h$", ""}, + {"sys/shm.h$", ""}, + {"sys/signalfd.h$", ""}, + {"sys/socket.h$", ""}, + {"sys/stat.h$", ""}, + {"sys/statfs.h$", ""}, + {"sys/statvfs.h$", ""}, + {"sys/swap.h$", ""}, + {"sys/syscall.h$", ""}, + {"sys/sysctl.h$", ""}, + {"sys/sysinfo.h$", ""}, + {"sys/syslog.h$", ""}, + {"sys/sysmacros.h$", ""}, + {"sys/termios.h$", ""}, + {"sys/time.h$", ""}, + {"sys/timeb.h$", ""}, + {"sys/timerfd.h$", ""}, + {"sys/times.h$", ""}, + {"sys/timex.h$", ""}, + {"sys/ttychars.h$", ""}, + {"sys/ttydefaults.h$", ""}, + {"sys/types.h$", ""}, + {"sys/ucontext.h$", ""}, + {"sys/uio.h$", ""}, + {"sys/un.h$", ""}, + {"sys/user.h$", ""}, + {"sys/ustat.h$", ""}, + {"sys/utsname.h$", ""}, + {"sys/vlimit.h$", ""}, + {"sys/vm86.h$", ""}, + {"sys/vtimes.h$", ""}, + {"sys/wait.h$", ""}, + {"sys/xattr.h$", ""}, + {"bits/epoll.h$", ""}, + {"bits/eventfd.h$", ""}, + {"bits/inotify.h$", ""}, + {"bits/ipc.h$", ""}, + {"bits/ipctypes.h$", ""}, + {"bits/mman-linux.h$", ""}, + {"bits/mman.h$", ""}, + {"bits/msq.h$", ""}, + {"bits/resource.h$", ""}, + {"bits/sem.h$", ""}, + {"bits/shm.h$", ""}, + {"bits/signalfd.h$", ""}, + {"bits/statfs.h$", ""}, + {"bits/statvfs.h$", ""}, + {"bits/timerfd.h$", ""}, + {"bits/utsname.h$", ""}, + {"bits/auxv.h$", ""}, + {"bits/byteswap-16.h$", ""}, + {"bits/byteswap.h$", ""}, + {"bits/confname.h$", ""}, + {"bits/dirent.h$", ""}, + {"bits/dlfcn.h$", ""}, + {"bits/elfclass.h$", ""}, + {"bits/endian.h$", ""}, + {"bits/environments.h$", ""}, + {"bits/fcntl-linux.h$", ""}, + {"bits/fcntl.h$", ""}, + {"bits/in.h$", ""}, + {"bits/ioctl-types.h$", ""}, + {"bits/ioctls.h$", ""}, + {"bits/link.h$", ""}, + {"bits/mqueue.h$", ""}, + {"bits/netdb.h$", ""}, + {"bits/param.h$", ""}, + {"bits/poll.h$", ""}, + {"bits/posix_opt.h$", ""}, + {"bits/pthreadtypes.h$", ""}, + {"bits/sched.h$", ""}, + {"bits/select.h$", ""}, + {"bits/semaphore.h$", ""}, + {"bits/sigthread.h$", ""}, + {"bits/sockaddr.h$", ""}, + {"bits/socket.h$", ""}, + {"bits/socket_type.h$", ""}, + {"bits/stab.def$", ""}, + {"bits/stat.h$", ""}, + {"bits/stropts.h$", ""}, + {"bits/syscall.h$", ""}, + {"bits/syslog-path.h$", ""}, + {"bits/termios.h$", ""}, + {"bits/types.h$", ""}, + {"bits/typesizes.h$", ""}, + {"bits/uio.h$", ""}, + {"bits/ustat.h$", ""}, + {"bits/utmp.h$", ""}, + {"bits/utmpx.h$", ""}, + {"bits/waitflags.h$", ""}, + {"bits/waitstatus.h$", ""}, + {"bits/xtitypes.h$", ""}, + }; + for (const auto &Pair : SystemHeaderMap) + Includes->addRegexMapping(Pair.first, Pair.second); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/CanonicalIncludes.h b/clangd/index/CanonicalIncludes.h new file mode 100644 index 000000000..a2fdb04ec --- /dev/null +++ b/clangd/index/CanonicalIncludes.h @@ -0,0 +1,93 @@ +//===-- CanonicalIncludes.h - remap #include header -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// At indexing time, we decide which file to #included for a symbol. +// Usually this is the file with the canonical decl, but there are exceptions: +// - private headers may have pragmas pointing to the matching public header. +// (These are "IWYU" pragmas, named after the include-what-you-use tool). +// - the standard library is implemented in many files, without any pragmas. +// We have a lookup table for common standard library implementations. +// libstdc++ puts char_traits in bits/char_traits.h, but we #include . +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_CANONICALINCLUDES_H + +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include +#include +#include + +namespace clang { +namespace clangd { + +/// Maps a definition location onto an #include file, based on a set of filename +/// rules. +/// Only const methods (i.e. mapHeader) in this class are thread safe. +class CanonicalIncludes { +public: + CanonicalIncludes() = default; + + /// Adds a string-to-string mapping from \p Path to \p CanonicalPath. + void addMapping(llvm::StringRef Path, llvm::StringRef CanonicalPath); + + /// Maps all files matching \p RE to \p CanonicalPath + void addRegexMapping(llvm::StringRef RE, llvm::StringRef CanonicalPath); + + /// Sets the canonical include for any symbol with \p QualifiedName. + /// Symbol mappings take precedence over header mappings. + void addSymbolMapping(llvm::StringRef QualifiedName, + llvm::StringRef CanonicalPath); + + /// Returns the canonical include for symbol with \p QualifiedName. + /// \p Headers is the include stack: Headers.front() is the file declaring the + /// symbol, and Headers.back() is the main file. + llvm::StringRef mapHeader(llvm::ArrayRef Headers, + llvm::StringRef QualifiedName) const; + +private: + // A map from header patterns to header names. This needs to be mutable so + // that we can match again a Regex in a const function member. + // FIXME(ioeric): All the regexes we have so far are suffix matches. The + // performance could be improved by allowing only suffix matches instead of + // arbitrary regexes. + mutable std::vector> + RegexHeaderMappingTable; + // A map from fully qualified symbol names to header names. + llvm::StringMap SymbolMapping; + // Guards Regex matching as it's not thread-safe. + mutable std::mutex RegexMutex; +}; + +/// Returns a CommentHandler that parses pragma comment on include files to +/// determine when we should include a different header from the header that +/// directly defines a symbol. Mappinps are registered with \p Includes. +/// +/// Currently it only supports IWYU private pragma: +/// https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md#iwyu-pragma-private +std::unique_ptr +collectIWYUHeaderMaps(CanonicalIncludes *Includes); + +/// Adds mapping for system headers and some special symbols (e.g. STL symbols +/// in need to be mapped individually). Approximately, the following +/// system headers are handled: +/// - C++ standard library e.g. bits/basic_string.h$ -> +/// - Posix library e.g. bits/pthreadtypes.h$ -> +/// - Compiler extensions, e.g. include/avx512bwintrin.h$ -> +/// The mapping is hardcoded and hand-maintained, so it might not cover all +/// headers. +void addSystemHeadersMapping(CanonicalIncludes *Includes); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_HEADERMAPCOLLECTOR_H diff --git a/clangd/index/FileIndex.cpp b/clangd/index/FileIndex.cpp new file mode 100644 index 000000000..407e86a2c --- /dev/null +++ b/clangd/index/FileIndex.cpp @@ -0,0 +1,109 @@ +//===--- FileIndex.cpp - Indexes for files. ------------------------ C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FileIndex.h" +#include "SymbolCollector.h" +#include "clang/Index/IndexingAction.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace clangd { + +SymbolSlab indexAST(ASTContext &AST, std::shared_ptr PP, + llvm::ArrayRef URISchemes) { + SymbolCollector::Options CollectorOpts; + // FIXME(ioeric): we might also want to collect include headers. We would need + // to make sure all includes are canonicalized (with CanonicalIncludes), which + // is not trivial given the current way of collecting symbols: we only have + // AST at this point, but we also need preprocessor callbacks (e.g. + // CommentHandler for IWYU pragma) to canonicalize includes. + CollectorOpts.CollectIncludePath = false; + CollectorOpts.CountReferences = false; + if (!URISchemes.empty()) + CollectorOpts.URISchemes = URISchemes; + CollectorOpts.Origin = SymbolOrigin::Dynamic; + + SymbolCollector Collector(std::move(CollectorOpts)); + Collector.setPreprocessor(PP); + index::IndexingOptions IndexOpts; + // We only need declarations, because we don't count references. + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; + IndexOpts.IndexFunctionLocals = false; + + std::vector TopLevelDecls( + AST.getTranslationUnitDecl()->decls().begin(), + AST.getTranslationUnitDecl()->decls().end()); + index::indexTopLevelDecls(AST, TopLevelDecls, Collector, IndexOpts); + + return Collector.takeSymbols(); +} + +FileIndex::FileIndex(std::vector URISchemes) + : URISchemes(std::move(URISchemes)) {} + +void FileSymbols::update(PathRef Path, std::unique_ptr Slab) { + std::lock_guard Lock(Mutex); + if (!Slab) + FileToSlabs.erase(Path); + else + FileToSlabs[Path] = std::move(Slab); +} + +std::shared_ptr> FileSymbols::allSymbols() { + // The snapshot manages life time of symbol slabs and provides pointers of all + // symbols in all slabs. + struct Snapshot { + std::vector Pointers; + std::vector> KeepAlive; + }; + auto Snap = std::make_shared(); + { + std::lock_guard Lock(Mutex); + + for (const auto &FileAndSlab : FileToSlabs) { + Snap->KeepAlive.push_back(FileAndSlab.second); + for (const auto &Iter : *FileAndSlab.second) + Snap->Pointers.push_back(&Iter); + } + } + auto *Pointers = &Snap->Pointers; + // Use aliasing constructor to keep the snapshot alive along with the + // pointers. + return {std::move(Snap), Pointers}; +} + +void FileIndex::update(PathRef Path, ASTContext *AST, + std::shared_ptr PP) { + if (!AST) { + FSymbols.update(Path, nullptr); + } else { + assert(PP); + auto Slab = llvm::make_unique(); + *Slab = indexAST(*AST, PP, URISchemes); + FSymbols.update(Path, std::move(Slab)); + } + auto Symbols = FSymbols.allSymbols(); + Index.build(std::move(Symbols)); +} + +bool FileIndex::fuzzyFind( + const FuzzyFindRequest &Req, + llvm::function_ref Callback) const { + return Index.fuzzyFind(Req, Callback); +} + +void FileIndex::lookup( + const LookupRequest &Req, + llvm::function_ref Callback) const { + Index.lookup(Req, Callback); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/FileIndex.h b/clangd/index/FileIndex.h new file mode 100644 index 000000000..1af37d19b --- /dev/null +++ b/clangd/index/FileIndex.h @@ -0,0 +1,91 @@ +//===--- FileIndex.h - Index for files. ---------------------------- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// FileIndex implements SymbolIndex for symbols from a set of files. Symbols are +// maintained at source-file granuality (e.g. with ASTs), and files can be +// updated dynamically. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_FILEINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_FILEINDEX_H + +#include "../ClangdUnit.h" +#include "Index.h" +#include "MemIndex.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace clangd { + +/// \brief A container of Symbols from several source files. It can be updated +/// at source-file granularity, replacing all symbols from one file with a new +/// set. +/// +/// This implements a snapshot semantics for symbols in a file. Each update to a +/// file will create a new snapshot for all symbols in the file. Snapshots are +/// managed with shared pointers that are shared between this class and the +/// users. For each file, this class only stores a pointer pointing to the +/// newest snapshot, and an outdated snapshot is deleted by the last owner of +/// the snapshot, either this class or the symbol index. +/// +/// The snapshot semantics keeps critical sections minimal since we only need +/// locking when we swap or obtain refereces to snapshots. +class FileSymbols { +public: + /// \brief Updates all symbols in a file. If \p Slab is nullptr, symbols for + /// \p Path will be removed. + void update(PathRef Path, std::unique_ptr Slab); + + // The shared_ptr keeps the symbols alive + std::shared_ptr> allSymbols(); + +private: + mutable std::mutex Mutex; + + /// \brief Stores the latest snapshots for all active files. + llvm::StringMap> FileToSlabs; +}; + +/// \brief This manages symbls from files and an in-memory index on all symbols. +class FileIndex : public SymbolIndex { +public: + /// If URISchemes is empty, the default schemes in SymbolCollector will be + /// used. + FileIndex(std::vector URISchemes = {}); + + /// \brief Update symbols in \p Path with symbols in \p AST. If \p AST is + /// nullptr, this removes all symbols in the file. + /// If \p AST is not null, \p PP cannot be null and it should be the + /// preprocessor that was used to build \p AST. + void update(PathRef Path, ASTContext *AST, std::shared_ptr PP); + + bool + fuzzyFind(const FuzzyFindRequest &Req, + llvm::function_ref Callback) const override; + + void lookup(const LookupRequest &Req, + llvm::function_ref Callback) const override; + +private: + FileSymbols FSymbols; + MemIndex Index; + std::vector URISchemes; +}; + +/// Retrieves namespace and class level symbols in \p AST. +/// Exposed to assist in unit tests. +/// If URISchemes is empty, the default schemes in SymbolCollector will be used. +SymbolSlab indexAST(ASTContext &AST, std::shared_ptr PP, + llvm::ArrayRef URISchemes = {}); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_FILEINDEX_H diff --git a/clangd/index/Index.cpp b/clangd/index/Index.cpp new file mode 100644 index 000000000..1ae3d5425 --- /dev/null +++ b/clangd/index/Index.cpp @@ -0,0 +1,138 @@ +//===--- Index.cpp -----------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Index.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/SHA1.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +raw_ostream &operator<<(raw_ostream &OS, const SymbolLocation &L) { + if (!L) + return OS << "(none)"; + return OS << L.FileURI << "[" << L.Start.Line << ":" << L.Start.Column << "-" + << L.End.Line << ":" << L.End.Column << ")"; +} + +SymbolID::SymbolID(StringRef USR) + : HashValue(SHA1::hash(arrayRefFromStringRef(USR))) {} + +raw_ostream &operator<<(raw_ostream &OS, const SymbolID &ID) { + OS << toHex(toStringRef(ID.HashValue)); + return OS; +} + +std::string SymbolID::str() const { + std::string ID; + llvm::raw_string_ostream OS(ID); + OS << *this; + return OS.str(); +} + +void operator>>(StringRef Str, SymbolID &ID) { + std::string HexString = fromHex(Str); + assert(HexString.size() == ID.HashValue.size()); + std::copy(HexString.begin(), HexString.end(), ID.HashValue.begin()); +} + +raw_ostream &operator<<(raw_ostream &OS, SymbolOrigin O) { + if (O == SymbolOrigin::Unknown) + return OS << "unknown"; + constexpr static char Sigils[] = "ADSM4567"; + for (unsigned I = 0; I < sizeof(Sigils); ++I) + if (static_cast(O) & 1u << I) + OS << Sigils[I]; + return OS; +} + +raw_ostream &operator<<(raw_ostream &OS, const Symbol &S) { + return OS << S.Scope << S.Name; +} + +double quality(const Symbol &S) { + // This avoids a sharp gradient for tail symbols, and also neatly avoids the + // question of whether 0 references means a bad symbol or missing data. + if (S.References < 3) + return 1; + return std::log(S.References); +} + +SymbolSlab::const_iterator SymbolSlab::find(const SymbolID &ID) const { + auto It = std::lower_bound(Symbols.begin(), Symbols.end(), ID, + [](const Symbol &S, const SymbolID &I) { + return S.ID < I; + }); + if (It != Symbols.end() && It->ID == ID) + return It; + return Symbols.end(); +} + +// Copy the underlying data of the symbol into the owned arena. +static void own(Symbol &S, DenseSet &Strings, + BumpPtrAllocator &Arena) { + // Intern replaces V with a reference to the same string owned by the arena. + auto Intern = [&](StringRef &V) { + auto R = Strings.insert(V); + if (R.second) { // New entry added to the table, copy the string. + *R.first = V.copy(Arena); + } + V = *R.first; + }; + + // We need to copy every StringRef field onto the arena. + Intern(S.Name); + Intern(S.Scope); + Intern(S.CanonicalDeclaration.FileURI); + Intern(S.Definition.FileURI); + + Intern(S.Signature); + Intern(S.CompletionSnippetSuffix); + + if (S.Detail) { + // Copy values of StringRefs into arena. + auto *Detail = Arena.Allocate(); + *Detail = *S.Detail; + // Intern the actual strings. + Intern(Detail->Documentation); + Intern(Detail->ReturnType); + Intern(Detail->IncludeHeader); + // Replace the detail pointer with our copy. + S.Detail = Detail; + } +} + +void SymbolSlab::Builder::insert(const Symbol &S) { + auto R = SymbolIndex.try_emplace(S.ID, Symbols.size()); + if (R.second) { + Symbols.push_back(S); + own(Symbols.back(), Strings, Arena); + } else { + auto &Copy = Symbols[R.first->second] = S; + own(Copy, Strings, Arena); + } +} + +SymbolSlab SymbolSlab::Builder::build() && { + Symbols = {Symbols.begin(), Symbols.end()}; // Force shrink-to-fit. + // Sort symbols so the slab can binary search over them. + std::sort(Symbols.begin(), Symbols.end(), + [](const Symbol &L, const Symbol &R) { return L.ID < R.ID; }); + // We may have unused strings from overwritten symbols. Build a new arena. + BumpPtrAllocator NewArena; + DenseSet Strings; + for (auto &S : Symbols) + own(S, Strings, NewArena); + return SymbolSlab(std::move(NewArena), std::move(Symbols)); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/Index.h b/clangd/index/Index.h new file mode 100644 index 000000000..18ed05f73 --- /dev/null +++ b/clangd/index/Index.h @@ -0,0 +1,343 @@ +//===--- Index.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_INDEX_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H + +#include "clang/Index/IndexSymbol.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringExtras.h" +#include +#include + +namespace clang { +namespace clangd { + +struct SymbolLocation { + // Specify a position (Line, Column) of symbol. Using Line/Column allows us to + // build LSP responses without reading the file content. + struct Position { + uint32_t Line = 0; // 0-based + // Using UTF-16 code units. + uint32_t Column = 0; // 0-based + bool operator==(const Position& P) const { + return Line == P.Line && Column == P.Column; + } + }; + + // The URI of the source file where a symbol occurs. + llvm::StringRef FileURI; + + /// The symbol range, using half-open range [Start, End). + Position Start; + Position End; + + explicit operator bool() const { return !FileURI.empty(); } + bool operator==(const SymbolLocation& Loc) const { + return std::tie(FileURI, Start, End) == + std::tie(Loc.FileURI, Loc.Start, Loc.End); + } +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &); + +// The class identifies a particular C++ symbol (class, function, method, etc). +// +// As USRs (Unified Symbol Resolution) could be large, especially for functions +// with long type arguments, SymbolID is using 160-bits SHA1(USR) values to +// guarantee the uniqueness of symbols while using a relatively small amount of +// memory (vs storing USRs directly). +// +// SymbolID can be used as key in the symbol indexes to lookup the symbol. +class SymbolID { +public: + SymbolID() = default; + explicit SymbolID(llvm::StringRef USR); + + bool operator==(const SymbolID &Sym) const { + return HashValue == Sym.HashValue; + } + bool operator<(const SymbolID &Sym) const { + return HashValue < Sym.HashValue; + } + + // Returns a 40-bytes hex encoded string. + std::string str() const; + +private: + static constexpr unsigned HashByteLength = 20; + + friend llvm::hash_code hash_value(const SymbolID &ID) { + // We already have a good hash, just return the first bytes. + static_assert(sizeof(size_t) <= HashByteLength, "size_t longer than SHA1!"); + size_t Result; + memcpy(&Result, ID.HashValue.data(), sizeof(size_t)); + return llvm::hash_code(Result); + } + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const SymbolID &ID); + friend void operator>>(llvm::StringRef Str, SymbolID &ID); + + std::array HashValue; +}; + +// Write SymbolID into the given stream. SymbolID is encoded as a 40-bytes +// hex string. +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); + +// Construct SymbolID from a hex string. +// The HexStr is required to be a 40-bytes hex string, which is encoded from the +// "<<" operator. +void operator>>(llvm::StringRef HexStr, SymbolID &ID); + +} // namespace clangd +} // namespace clang +namespace llvm { +// Support SymbolIDs as DenseMap keys. +template <> struct DenseMapInfo { + static inline clang::clangd::SymbolID getEmptyKey() { + static clang::clangd::SymbolID EmptyKey("EMPTYKEY"); + return EmptyKey; + } + static inline clang::clangd::SymbolID getTombstoneKey() { + static clang::clangd::SymbolID TombstoneKey("TOMBSTONEKEY"); + return TombstoneKey; + } + static unsigned getHashValue(const clang::clangd::SymbolID &Sym) { + return hash_value(Sym); + } + static bool isEqual(const clang::clangd::SymbolID &LHS, + const clang::clangd::SymbolID &RHS) { + return LHS == RHS; + } +}; +} // namespace llvm +namespace clang { +namespace clangd { + +// Describes the source of information about a symbol. +// Mainly useful for debugging, e.g. understanding code completion reuslts. +// This is a bitfield as information can be combined from several sources. +enum class SymbolOrigin : uint8_t { + Unknown = 0, + AST = 1 << 0, // Directly from the AST (indexes should not set this). + Dynamic = 1 << 1, // From the dynamic index of opened files. + Static = 1 << 2, // From the static, externally-built index. + Merge = 1 << 3, // A non-trivial index merge was performed. + // Remaining bits reserved for index implementations. +}; +inline SymbolOrigin operator|(SymbolOrigin A, SymbolOrigin B) { + return static_cast(static_cast(A) | + static_cast(B)); +} +inline SymbolOrigin &operator|=(SymbolOrigin &A, SymbolOrigin B) { + return A = A | B; +} +inline SymbolOrigin operator&(SymbolOrigin A, SymbolOrigin B) { + return static_cast(static_cast(A) & + static_cast(B)); +} +raw_ostream &operator<<(raw_ostream &, SymbolOrigin); + +// The class presents a C++ symbol, e.g. class, function. +// +// WARNING: Symbols do not own much of their underlying data - typically strings +// are owned by a SymbolSlab. They should be treated as non-owning references. +// Copies are shallow. +// When adding new unowned data fields to Symbol, remember to update: +// - SymbolSlab::Builder in Index.cpp, to copy them to the slab's storage. +// - mergeSymbol in Merge.cpp, to properly combine two Symbols. +// +// A fully documented symbol can be split as: +// size_type std::map::count(const K& key) const +// | Return | Scope |Name| Signature | +// We split up these components to allow display flexibility later. +struct Symbol { + // The ID of the symbol. + SymbolID ID; + // The symbol information, like symbol kind. + index::SymbolInfo SymInfo; + // The unqualified name of the symbol, e.g. "bar" (for ns::bar). + llvm::StringRef Name; + // The containing namespace. e.g. "" (global), "ns::" (top-level namespace). + llvm::StringRef Scope; + // The location of the symbol's definition, if one was found. + // This just covers the symbol name (e.g. without class/function body). + SymbolLocation Definition; + // The location of the preferred declaration of the symbol. + // This just covers the symbol name. + // This may be the same as Definition. + // + // A C++ symbol may have multiple declarations, and we pick one to prefer. + // * For classes, the canonical declaration should be the definition. + // * For non-inline functions, the canonical declaration typically appears + // in the ".h" file corresponding to the definition. + SymbolLocation CanonicalDeclaration; + // The number of translation units that reference this symbol from their main + // file. This number is only meaningful if aggregated in an index. + unsigned References = 0; + /// Whether or not this symbol is meant to be used for the code completion. + /// See also isIndexedForCodeCompletion(). + bool IsIndexedForCodeCompletion = false; + /// Where this symbol came from. Usually an index provides a constant value. + SymbolOrigin Origin = SymbolOrigin::Unknown; + /// A brief description of the symbol that can be appended in the completion + /// candidate list. For example, "(X x, Y y) const" is a function signature. + llvm::StringRef Signature; + /// What to insert when completing this symbol, after the symbol name. + /// This is in LSP snippet syntax (e.g. "({$0})" for a no-args function). + /// (When snippets are disabled, the symbol name alone is used). + llvm::StringRef CompletionSnippetSuffix; + + /// Optional symbol details that are not required to be set. For example, an + /// index fuzzy match can return a large number of symbol candidates, and it + /// is preferable to send only core symbol information in the batched results + /// and have clients resolve full symbol information for a specific candidate + /// if needed. + struct Details { + /// Documentation including comment for the symbol declaration. + llvm::StringRef Documentation; + /// Type when this symbol is used in an expression. (Short display form). + /// e.g. return type of a function, or type of a variable. + llvm::StringRef ReturnType; + /// This can be either a URI of the header to be #include'd for this symbol, + /// or a literal header quoted with <> or "" that is suitable to be included + /// directly. When this is a URI, the exact #include path needs to be + /// calculated according to the URI scheme. + /// + /// This is a canonical include for the symbol and can be different from + /// FileURI in the CanonicalDeclaration. + llvm::StringRef IncludeHeader; + }; + + // Optional details of the symbol. + const Details *Detail = nullptr; + + // FIXME: add all occurrences support. + // FIXME: add extra fields for index scoring signals. +}; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Symbol &S); + +// Computes query-independent quality score for a Symbol. +// This currently falls in the range [1, ln(#indexed documents)]. +// FIXME: this should probably be split into symbol -> signals +// and signals -> score, so it can be reused for Sema completions. +double quality(const Symbol &S); + +// An immutable symbol container that stores a set of symbols. +// The container will maintain the lifetime of the symbols. +class SymbolSlab { +public: + using const_iterator = std::vector::const_iterator; + using iterator = const_iterator; + + SymbolSlab() = default; + + const_iterator begin() const { return Symbols.begin(); } + const_iterator end() const { return Symbols.end(); } + const_iterator find(const SymbolID &SymID) const; + + size_t size() const { return Symbols.size(); } + // Estimates the total memory usage. + size_t bytes() const { + return sizeof(*this) + Arena.getTotalMemory() + + Symbols.capacity() * sizeof(Symbol); + } + + // SymbolSlab::Builder is a mutable container that can 'freeze' to SymbolSlab. + // The frozen SymbolSlab will use less memory. + class Builder { + public: + // Adds a symbol, overwriting any existing one with the same ID. + // This is a deep copy: underlying strings will be owned by the slab. + void insert(const Symbol &S); + + // Returns the symbol with an ID, if it exists. Valid until next insert(). + const Symbol *find(const SymbolID &ID) { + auto I = SymbolIndex.find(ID); + return I == SymbolIndex.end() ? nullptr : &Symbols[I->second]; + } + + // Consumes the builder to finalize the slab. + SymbolSlab build() &&; + + private: + llvm::BumpPtrAllocator Arena; + // Intern table for strings. Contents are on the arena. + llvm::DenseSet Strings; + std::vector Symbols; + // Values are indices into Symbols vector. + llvm::DenseMap SymbolIndex; + }; + +private: + SymbolSlab(llvm::BumpPtrAllocator Arena, std::vector Symbols) + : Arena(std::move(Arena)), Symbols(std::move(Symbols)) {} + + llvm::BumpPtrAllocator Arena; // Owns Symbol data that the Symbols do not. + std::vector Symbols; // Sorted by SymbolID to allow lookup. +}; + +struct FuzzyFindRequest { + /// \brief A query string for the fuzzy find. This is matched against symbols' + /// un-qualified identifiers and should not contain qualifiers like "::". + std::string Query; + /// \brief If this is non-empty, symbols must be in at least one of the scopes + /// (e.g. namespaces) excluding nested scopes. For example, if a scope "xyz::" + /// is provided, the matched symbols must be defined in namespace xyz but not + /// namespace xyz::abc. + /// + /// The global scope is "", a top level scope is "foo::", etc. + std::vector Scopes; + /// \brief The number of top candidates to return. The index may choose to + /// return more than this, e.g. if it doesn't know which candidates are best. + size_t MaxCandidateCount = UINT_MAX; + /// If set to true, only symbols for completion support will be considered. + bool RestrictForCodeCompletion = false; + /// Contextually relevant files (e.g. the file we're code-completing in). + /// Paths should be absolute. + std::vector ProximityPaths; +}; + +struct LookupRequest { + llvm::DenseSet IDs; +}; + +/// \brief Interface for symbol indexes that can be used for searching or +/// matching symbols among a set of symbols based on names or unique IDs. +class SymbolIndex { +public: + virtual ~SymbolIndex() = default; + + /// \brief Matches symbols in the index fuzzily and applies \p Callback on + /// each matched symbol before returning. + /// If returned Symbols are used outside Callback, they must be deep-copied! + /// + /// Returns true if there may be more results (limited by MaxCandidateCount). + virtual bool + fuzzyFind(const FuzzyFindRequest &Req, + llvm::function_ref Callback) const = 0; + + /// Looks up symbols with any of the given symbol IDs and applies \p Callback + /// on each matched symbol. + /// The returned symbol must be deep-copied if it's used outside Callback. + virtual void + lookup(const LookupRequest &Req, + llvm::function_ref Callback) const = 0; + + // FIXME: add interfaces for more index use cases: + // - getAllOccurrences(SymbolID); +}; + +} // namespace clangd +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H diff --git a/clangd/index/MemIndex.cpp b/clangd/index/MemIndex.cpp new file mode 100644 index 000000000..1a38386bd --- /dev/null +++ b/clangd/index/MemIndex.cpp @@ -0,0 +1,91 @@ +//===--- MemIndex.cpp - Dynamic in-memory symbol index. ----------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-------------------------------------------------------------------===// + +#include "MemIndex.h" +#include "../FuzzyMatch.h" +#include "../Logger.h" +#include + +namespace clang { +namespace clangd { + +void MemIndex::build(std::shared_ptr> Syms) { + llvm::DenseMap TempIndex; + for (const Symbol *Sym : *Syms) + TempIndex[Sym->ID] = Sym; + + // Swap out the old symbols and index. + { + std::lock_guard Lock(Mutex); + Index = std::move(TempIndex); + Symbols = std::move(Syms); // Relase old symbols. + } +} + +bool MemIndex::fuzzyFind( + const FuzzyFindRequest &Req, + llvm::function_ref Callback) const { + assert(!StringRef(Req.Query).contains("::") && + "There must be no :: in query."); + + std::priority_queue> Top; + FuzzyMatcher Filter(Req.Query); + bool More = false; + { + std::lock_guard Lock(Mutex); + for (const auto Pair : Index) { + const Symbol *Sym = Pair.second; + + // Exact match against all possible scopes. + if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope)) + continue; + if (Req.RestrictForCodeCompletion && !Sym->IsIndexedForCodeCompletion) + continue; + + if (auto Score = Filter.match(Sym->Name)) { + Top.emplace(-*Score * quality(*Sym), Sym); + if (Top.size() > Req.MaxCandidateCount) { + More = true; + Top.pop(); + } + } + } + for (; !Top.empty(); Top.pop()) + Callback(*Top.top().second); + } + return More; +} + +void MemIndex::lookup(const LookupRequest &Req, + llvm::function_ref Callback) const { + for (const auto &ID : Req.IDs) { + auto I = Index.find(ID); + if (I != Index.end()) + Callback(*I->second); + } +} + +std::unique_ptr MemIndex::build(SymbolSlab Slab) { + struct Snapshot { + SymbolSlab Slab; + std::vector Pointers; + }; + auto Snap = std::make_shared(); + Snap->Slab = std::move(Slab); + for (auto &Sym : Snap->Slab) + Snap->Pointers.push_back(&Sym); + auto S = std::shared_ptr>(std::move(Snap), + &Snap->Pointers); + auto MemIdx = llvm::make_unique(); + MemIdx->build(std::move(S)); + return std::move(MemIdx); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/MemIndex.h b/clangd/index/MemIndex.h new file mode 100644 index 000000000..3147a6c21 --- /dev/null +++ b/clangd/index/MemIndex.h @@ -0,0 +1,49 @@ +//===--- MemIndex.h - Dynamic in-memory symbol index. -------------- 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_INDEX_MEMINDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_MEMINDEX_H + +#include "Index.h" +#include + +namespace clang { +namespace clangd { + +/// \brief This implements an index for a (relatively small) set of symbols that +/// can be easily managed in memory. +class MemIndex : public SymbolIndex { +public: + /// \brief (Re-)Build index for `Symbols`. All symbol pointers must remain + /// accessible as long as `Symbols` is kept alive. + void build(std::shared_ptr> Symbols); + + /// \brief Build index from a symbol slab. + static std::unique_ptr build(SymbolSlab Slab); + + bool + fuzzyFind(const FuzzyFindRequest &Req, + llvm::function_ref Callback) const override; + + virtual void + lookup(const LookupRequest &Req, + llvm::function_ref Callback) const override; + +private: + std::shared_ptr> Symbols; + // Index is a set of symbols that are deduplicated by symbol IDs. + // FIXME: build smarter index structure. + llvm::DenseMap Index; + mutable std::mutex Mutex; +}; + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_MEMINDEX_H diff --git a/clangd/index/Merge.cpp b/clangd/index/Merge.cpp new file mode 100644 index 000000000..da31f8b63 --- /dev/null +++ b/clangd/index/Merge.cpp @@ -0,0 +1,128 @@ +//===--- Merge.h ------------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +#include "Merge.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/raw_ostream.h" +namespace clang { +namespace clangd { +namespace { +using namespace llvm; + +class MergedIndex : public SymbolIndex { + public: + MergedIndex(const SymbolIndex *Dynamic, const SymbolIndex *Static) + : Dynamic(Dynamic), Static(Static) {} + + // FIXME: Deleted symbols in dirty files are still returned (from Static). + // To identify these eliminate these, we should: + // - find the generating file from each Symbol which is Static-only + // - ask Dynamic if it has that file (needs new SymbolIndex method) + // - if so, drop the Symbol. + bool fuzzyFind(const FuzzyFindRequest &Req, + function_ref Callback) const override { + // We can't step through both sources in parallel. So: + // 1) query all dynamic symbols, slurping results into a slab + // 2) query the static symbols, for each one: + // a) if it's not in the dynamic slab, yield it directly + // b) if it's in the dynamic slab, merge it and yield the result + // 3) now yield all the dynamic symbols we haven't processed. + bool More = false; // We'll be incomplete if either source was. + SymbolSlab::Builder DynB; + More |= Dynamic->fuzzyFind(Req, [&](const Symbol &S) { DynB.insert(S); }); + SymbolSlab Dyn = std::move(DynB).build(); + + DenseSet SeenDynamicSymbols; + Symbol::Details Scratch; + More |= Static->fuzzyFind(Req, [&](const Symbol &S) { + auto DynS = Dyn.find(S.ID); + if (DynS == Dyn.end()) + return Callback(S); + SeenDynamicSymbols.insert(S.ID); + Callback(mergeSymbol(*DynS, S, &Scratch)); + }); + for (const Symbol &S : Dyn) + if (!SeenDynamicSymbols.count(S.ID)) + Callback(S); + return More; + } + + void + lookup(const LookupRequest &Req, + llvm::function_ref Callback) const override { + SymbolSlab::Builder B; + + Dynamic->lookup(Req, [&](const Symbol &S) { B.insert(S); }); + + auto RemainingIDs = Req.IDs; + Symbol::Details Scratch; + Static->lookup(Req, [&](const Symbol &S) { + const Symbol *Sym = B.find(S.ID); + RemainingIDs.erase(S.ID); + if (!Sym) + Callback(S); + else + Callback(mergeSymbol(*Sym, S, &Scratch)); + }); + for (const auto &ID : RemainingIDs) + if (const Symbol *Sym = B.find(ID)) + Callback(*Sym); + } + +private: + const SymbolIndex *Dynamic, *Static; +}; +} // namespace + +Symbol +mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) { + assert(L.ID == R.ID); + // We prefer information from TUs that saw the definition. + // Classes: this is the def itself. Functions: hopefully the header decl. + // If both did (or both didn't), continue to prefer L over R. + bool PreferR = R.Definition && !L.Definition; + Symbol S = PreferR ? R : L; // The target symbol we're merging into. + const Symbol &O = PreferR ? L : R; // The "other" less-preferred symbol. + + // For each optional field, fill it from O if missing in S. + // (It might be missing in O too, but that's a no-op). + if (!S.Definition) + S.Definition = O.Definition; + if (!S.CanonicalDeclaration) + S.CanonicalDeclaration = O.CanonicalDeclaration; + S.References += O.References; + if (S.Signature == "") + S.Signature = O.Signature; + if (S.CompletionSnippetSuffix == "") + S.CompletionSnippetSuffix = O.CompletionSnippetSuffix; + + if (O.Detail) { + if (S.Detail) { + // Copy into scratch space so we can merge. + *Scratch = *S.Detail; + if (Scratch->Documentation == "") + Scratch->Documentation = O.Detail->Documentation; + if (Scratch->ReturnType == "") + Scratch->ReturnType = O.Detail->ReturnType; + if (Scratch->IncludeHeader == "") + Scratch->IncludeHeader = O.Detail->IncludeHeader; + S.Detail = Scratch; + } else + S.Detail = O.Detail; + } + + S.Origin |= O.Origin | SymbolOrigin::Merge; + return S; +} + +std::unique_ptr mergeIndex(const SymbolIndex *Dynamic, + const SymbolIndex *Static) { + return llvm::make_unique(Dynamic, Static); +} +} // namespace clangd +} // namespace clang diff --git a/clangd/index/Merge.h b/clangd/index/Merge.h new file mode 100644 index 000000000..b9b58fd9f --- /dev/null +++ b/clangd/index/Merge.h @@ -0,0 +1,29 @@ +//===--- Merge.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_INDEX_MERGE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_MERGE_H +#include "Index.h" +namespace clang { +namespace clangd { + +// Merge symbols L and R, preferring data from L in case of conflict. +// The two symbols must have the same ID. +// Returned symbol may contain data owned by either source. +Symbol mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch); + +// mergedIndex returns a composite index based on two provided Indexes: +// - the Dynamic index covers few files, but is relatively up-to-date. +// - the Static index covers a bigger set of files, but is relatively stale. +// The returned index attempts to combine results, and avoid duplicates. +std::unique_ptr mergeIndex(const SymbolIndex *Dynamic, + const SymbolIndex *Static); + +} // namespace clangd +} // namespace clang +#endif diff --git a/clangd/index/SymbolCollector.cpp b/clangd/index/SymbolCollector.cpp new file mode 100644 index 000000000..5c80f3845 --- /dev/null +++ b/clangd/index/SymbolCollector.cpp @@ -0,0 +1,518 @@ +//===--- SymbolCollector.cpp -------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SymbolCollector.h" +#include "../AST.h" +#include "../CodeComplete.h" +#include "../CodeCompletionStrings.h" +#include "../Logger.h" +#include "../SourceCode.h" +#include "../URI.h" +#include "CanonicalIncludes.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Index/USRGeneration.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace clangd { + +namespace { +/// If \p ND is a template specialization, returns the described template. +/// Otherwise, returns \p ND. +const NamedDecl &getTemplateOrThis(const NamedDecl &ND) { + if (auto T = ND.getDescribedTemplate()) + return *T; + return ND; +} + +// Returns a URI of \p Path. Firstly, this makes the \p Path absolute using the +// current working directory of the given SourceManager if the Path is not an +// absolute path. If failed, this resolves relative paths against \p FallbackDir +// to get an absolute path. Then, this tries creating an URI for the absolute +// path with schemes specified in \p Opts. This returns an URI with the first +// working scheme, if there is any; otherwise, this returns None. +// +// The Path can be a path relative to the build directory, or retrieved from +// the SourceManager. +llvm::Optional toURI(const SourceManager &SM, StringRef Path, + const SymbolCollector::Options &Opts) { + llvm::SmallString<128> AbsolutePath(Path); + if (std::error_code EC = + SM.getFileManager().getVirtualFileSystem()->makeAbsolute( + AbsolutePath)) + log("Warning: could not make absolute file: {0}", EC.message()); + if (llvm::sys::path::is_absolute(AbsolutePath)) { + // Handle the symbolic link path case where the current working directory + // (getCurrentWorkingDirectory) is a symlink./ We always want to the real + // file path (instead of the symlink path) for the C++ symbols. + // + // Consider the following example: + // + // src dir: /project/src/foo.h + // current working directory (symlink): /tmp/build -> /project/src/ + // + // The file path of Symbol is "/project/src/foo.h" instead of + // "/tmp/build/foo.h" + if (const DirectoryEntry *Dir = SM.getFileManager().getDirectory( + llvm::sys::path::parent_path(AbsolutePath.str()))) { + StringRef DirName = SM.getFileManager().getCanonicalName(Dir); + SmallString<128> AbsoluteFilename; + llvm::sys::path::append(AbsoluteFilename, DirName, + llvm::sys::path::filename(AbsolutePath.str())); + AbsolutePath = AbsoluteFilename; + } + } else if (!Opts.FallbackDir.empty()) { + llvm::sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath); + } + + llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true); + + std::string ErrMsg; + for (const auto &Scheme : Opts.URISchemes) { + auto U = URI::create(AbsolutePath, Scheme); + if (U) + return U->toString(); + ErrMsg += llvm::toString(U.takeError()) + "\n"; + } + log("Failed to create an URI for file {0}: {1}", AbsolutePath, ErrMsg); + return llvm::None; +} + +// All proto generated headers should start with this line. +static const char *PROTO_HEADER_COMMENT = + "// Generated by the protocol buffer compiler. DO NOT EDIT!"; + +// Checks whether the decl is a private symbol in a header generated by +// protobuf compiler. +// To identify whether a proto header is actually generated by proto compiler, +// we check whether it starts with PROTO_HEADER_COMMENT. +// FIXME: make filtering extensible when there are more use cases for symbol +// filters. +bool isPrivateProtoDecl(const NamedDecl &ND) { + const auto &SM = ND.getASTContext().getSourceManager(); + auto Loc = findNameLoc(&ND); + auto FileName = SM.getFilename(Loc); + if (!FileName.endswith(".proto.h") && !FileName.endswith(".pb.h")) + return false; + auto FID = SM.getFileID(Loc); + // Double check that this is an actual protobuf header. + if (!SM.getBufferData(FID).startswith(PROTO_HEADER_COMMENT)) + return false; + + // ND without identifier can be operators. + if (ND.getIdentifier() == nullptr) + return false; + auto Name = ND.getIdentifier()->getName(); + if (!Name.contains('_')) + return false; + // Nested proto entities (e.g. Message::Nested) have top-level decls + // that shouldn't be used (Message_Nested). Ignore them completely. + // The nested entities are dangling type aliases, we may want to reconsider + // including them in the future. + // For enum constants, SOME_ENUM_CONSTANT is not private and should be + // indexed. Outer_INNER is private. This heuristic relies on naming style, it + // will include OUTER_INNER and exclude some_enum_constant. + // FIXME: the heuristic relies on naming style (i.e. no underscore in + // user-defined names) and can be improved. + return (ND.getKind() != Decl::EnumConstant) || + std::any_of(Name.begin(), Name.end(), islower); +} + +// We only collect #include paths for symbols that are suitable for global code +// completion, except for namespaces since #include path for a namespace is hard +// to define. +bool shouldCollectIncludePath(index::SymbolKind Kind) { + using SK = index::SymbolKind; + switch (Kind) { + case SK::Macro: + case SK::Enum: + case SK::Struct: + case SK::Class: + case SK::Union: + case SK::TypeAlias: + case SK::Using: + case SK::Function: + case SK::Variable: + case SK::EnumConstant: + return true; + default: + return false; + } +} + +/// Gets a canonical include (URI of the header or
or "header") for +/// header of \p Loc. +/// Returns None if fails to get include header for \p Loc. +llvm::Optional +getIncludeHeader(llvm::StringRef QName, const SourceManager &SM, + SourceLocation Loc, const SymbolCollector::Options &Opts) { + std::vector Headers; + // Collect the #include stack. + while (true) { + if (!Loc.isValid()) + break; + auto FilePath = SM.getFilename(Loc); + if (FilePath.empty()) + break; + Headers.push_back(FilePath); + if (SM.isInMainFile(Loc)) + break; + Loc = SM.getIncludeLoc(SM.getFileID(Loc)); + } + if (Headers.empty()) + return llvm::None; + llvm::StringRef Header = Headers[0]; + if (Opts.Includes) { + Header = Opts.Includes->mapHeader(Headers, QName); + if (Header.startswith("<") || Header.startswith("\"")) + return Header.str(); + } + return toURI(SM, Header, Opts); +} + +// Return the symbol location of the token at \p Loc. +llvm::Optional +getTokenLocation(SourceLocation TokLoc, const SourceManager &SM, + const SymbolCollector::Options &Opts, + const clang::LangOptions &LangOpts, + std::string &FileURIStorage) { + auto U = toURI(SM, SM.getFilename(TokLoc), Opts); + if (!U) + return llvm::None; + FileURIStorage = std::move(*U); + SymbolLocation Result; + Result.FileURI = FileURIStorage; + auto TokenLength = clang::Lexer::MeasureTokenLength(TokLoc, SM, LangOpts); + + auto CreatePosition = [&SM](SourceLocation Loc) { + auto LSPLoc = sourceLocToPosition(SM, Loc); + SymbolLocation::Position Pos; + Pos.Line = LSPLoc.line; + Pos.Column = LSPLoc.character; + return Pos; + }; + + Result.Start = CreatePosition(TokLoc); + auto EndLoc = TokLoc.getLocWithOffset(TokenLength); + Result.End = CreatePosition(EndLoc); + + return std::move(Result); +} + +// Checks whether \p ND is a definition of a TagDecl (class/struct/enum/union) +// in a header file, in which case clangd would prefer to use ND as a canonical +// declaration. +// FIXME: handle symbol types that are not TagDecl (e.g. functions), if using +// the first seen declaration as canonical declaration is not a good enough +// heuristic. +bool isPreferredDeclaration(const NamedDecl &ND, index::SymbolRoleSet Roles) { + using namespace clang::ast_matchers; + return (Roles & static_cast(index::SymbolRole::Definition)) && + llvm::isa(&ND) && + match(decl(isExpansionInMainFile()), ND, ND.getASTContext()).empty(); +} + +} // namespace + +SymbolCollector::SymbolCollector(Options Opts) : Opts(std::move(Opts)) {} + +void SymbolCollector::initialize(ASTContext &Ctx) { + ASTCtx = &Ctx; + CompletionAllocator = std::make_shared(); + CompletionTUInfo = + llvm::make_unique(CompletionAllocator); +} + +bool SymbolCollector::shouldCollectSymbol(const NamedDecl &ND, + ASTContext &ASTCtx, + const Options &Opts) { + using namespace clang::ast_matchers; + if (ND.isImplicit()) + return false; + // Skip anonymous declarations, e.g (anonymous enum/class/struct). + if (ND.getDeclName().isEmpty()) + return false; + + // FIXME: figure out a way to handle internal linkage symbols (e.g. static + // variables, function) defined in the .cc files. Also we skip the symbols + // in anonymous namespace as the qualifier names of these symbols are like + // `foo::::bar`, which need a special handling. + // In real world projects, we have a relatively large set of header files + // that define static variables (like "static const int A = 1;"), we still + // want to collect these symbols, although they cause potential ODR + // violations. + if (ND.isInAnonymousNamespace()) + return false; + + // We want most things but not "local" symbols such as symbols inside + // FunctionDecl, BlockDecl, ObjCMethodDecl and OMPDeclareReductionDecl. + // FIXME: Need a matcher for ExportDecl in order to include symbols declared + // within an export. + auto InNonLocalContext = hasDeclContext(anyOf( + translationUnitDecl(), namespaceDecl(), linkageSpecDecl(), recordDecl(), + enumDecl(), objcProtocolDecl(), objcInterfaceDecl(), objcCategoryDecl(), + objcCategoryImplDecl(), objcImplementationDecl())); + // Don't index template specializations and expansions in main files. + auto IsSpecialization = + anyOf(functionDecl(isExplicitTemplateSpecialization()), + cxxRecordDecl(isExplicitTemplateSpecialization()), + varDecl(isExplicitTemplateSpecialization())); + if (match(decl(allOf(unless(isExpansionInMainFile()), InNonLocalContext, + unless(IsSpecialization))), + ND, ASTCtx) + .empty()) + return false; + + // Avoid indexing internal symbols in protobuf generated headers. + if (isPrivateProtoDecl(ND)) + return false; + return true; +} + +// Always return true to continue indexing. +bool SymbolCollector::handleDeclOccurence( + const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) { + assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); + assert(CompletionAllocator && CompletionTUInfo); + assert(ASTNode.OrigD); + // If OrigD is an declaration associated with a friend declaration and it's + // not a definition, skip it. Note that OrigD is the occurrence that the + // collector is currently visiting. + if ((ASTNode.OrigD->getFriendObjectKind() != + Decl::FriendObjectKind::FOK_None) && + !(Roles & static_cast(index::SymbolRole::Definition))) + return true; + // A declaration created for a friend declaration should not be used as the + // canonical declaration in the index. Use OrigD instead, unless we've already + // picked a replacement for D + if (D->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None) + D = CanonicalDecls.try_emplace(D, ASTNode.OrigD).first->second; + const NamedDecl *ND = llvm::dyn_cast(D); + if (!ND) + return true; + + // Mark D as referenced if this is a reference coming from the main file. + // D may not be an interesting symbol, but it's cheaper to check at the end. + auto &SM = ASTCtx->getSourceManager(); + if (Opts.CountReferences && + (Roles & static_cast(index::SymbolRole::Reference)) && + SM.getFileID(SM.getSpellingLoc(Loc)) == SM.getMainFileID()) + ReferencedDecls.insert(ND); + + // Don't continue indexing if this is a mere reference. + if (!(Roles & static_cast(index::SymbolRole::Declaration) || + Roles & static_cast(index::SymbolRole::Definition))) + return true; + if (!shouldCollectSymbol(*ND, *ASTCtx, Opts)) + return true; + + llvm::SmallString<128> USR; + if (index::generateUSRForDecl(ND, USR)) + return true; + SymbolID ID(USR); + + const NamedDecl &OriginalDecl = *cast(ASTNode.OrigD); + const Symbol *BasicSymbol = Symbols.find(ID); + if (!BasicSymbol) // Regardless of role, ND is the canonical declaration. + BasicSymbol = addDeclaration(*ND, std::move(ID)); + else if (isPreferredDeclaration(OriginalDecl, Roles)) + // If OriginalDecl is preferred, replace the existing canonical + // declaration (e.g. a class forward declaration). There should be at most + // one duplicate as we expect to see only one preferred declaration per + // TU, because in practice they are definitions. + BasicSymbol = addDeclaration(OriginalDecl, std::move(ID)); + + if (Roles & static_cast(index::SymbolRole::Definition)) + addDefinition(OriginalDecl, *BasicSymbol); + return true; +} + +bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name, + const MacroInfo *MI, + index::SymbolRoleSet Roles, + SourceLocation Loc) { + if (!Opts.CollectMacro) + return true; + assert(PP.get()); + + const auto &SM = PP->getSourceManager(); + if (SM.isInMainFile(SM.getExpansionLoc(MI->getDefinitionLoc()))) + return true; + // Header guards are not interesting in index. Builtin macros don't have + // useful locations and are not needed for code completions. + if (MI->isUsedForHeaderGuard() || MI->isBuiltinMacro()) + return true; + + // Mark the macro as referenced if this is a reference coming from the main + // file. The macro may not be an interesting symbol, but it's cheaper to check + // at the end. + if (Opts.CountReferences && + (Roles & static_cast(index::SymbolRole::Reference)) && + SM.getFileID(SM.getSpellingLoc(Loc)) == SM.getMainFileID()) + ReferencedMacros.insert(Name); + // Don't continue indexing if this is a mere reference. + // FIXME: remove macro with ID if it is undefined. + if (!(Roles & static_cast(index::SymbolRole::Declaration) || + Roles & static_cast(index::SymbolRole::Definition))) + return true; + + llvm::SmallString<128> USR; + if (index::generateUSRForMacro(Name->getName(), MI->getDefinitionLoc(), SM, + USR)) + return true; + SymbolID ID(USR); + + // Only collect one instance in case there are multiple. + if (Symbols.find(ID) != nullptr) + return true; + + Symbol S; + S.ID = std::move(ID); + S.Name = Name->getName(); + S.IsIndexedForCodeCompletion = true; + S.SymInfo = index::getSymbolInfoForMacro(*MI); + std::string FileURI; + if (auto DeclLoc = getTokenLocation(MI->getDefinitionLoc(), SM, Opts, + PP->getLangOpts(), FileURI)) + S.CanonicalDeclaration = *DeclLoc; + + CodeCompletionResult SymbolCompletion(Name); + const auto *CCS = SymbolCompletion.CreateCodeCompletionStringForMacro( + *PP, *CompletionAllocator, *CompletionTUInfo); + std::string Signature; + std::string SnippetSuffix; + getSignature(*CCS, &Signature, &SnippetSuffix); + + std::string Include; + if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + if (auto Header = + getIncludeHeader(Name->getName(), SM, + SM.getExpansionLoc(MI->getDefinitionLoc()), Opts)) + Include = std::move(*Header); + } + S.Signature = Signature; + S.CompletionSnippetSuffix = SnippetSuffix; + Symbol::Details Detail; + Detail.IncludeHeader = Include; + S.Detail = &Detail; + Symbols.insert(S); + return true; +} + +void SymbolCollector::finish() { + // At the end of the TU, add 1 to the refcount of all referenced symbols. + auto IncRef = [this](const SymbolID &ID) { + if (const auto *S = Symbols.find(ID)) { + Symbol Inc = *S; + ++Inc.References; + Symbols.insert(Inc); + } + }; + for (const NamedDecl *ND : ReferencedDecls) { + llvm::SmallString<128> USR; + if (!index::generateUSRForDecl(ND, USR)) + IncRef(SymbolID(USR)); + } + if (Opts.CollectMacro) { + assert(PP); + for (const IdentifierInfo *II : ReferencedMacros) { + llvm::SmallString<128> USR; + if (const auto *MI = PP->getMacroDefinition(II).getMacroInfo()) + if (!index::generateUSRForMacro(II->getName(), MI->getDefinitionLoc(), + PP->getSourceManager(), USR)) + IncRef(SymbolID(USR)); + } + } + ReferencedDecls.clear(); + ReferencedMacros.clear(); +} + +const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, + SymbolID ID) { + auto &Ctx = ND.getASTContext(); + auto &SM = Ctx.getSourceManager(); + + Symbol S; + S.ID = std::move(ID); + std::string QName = printQualifiedName(ND); + std::tie(S.Scope, S.Name) = splitQualifiedName(QName); + // FIXME: this returns foo:bar: for objective-C methods, we prefer only foo: + // for consistency with CodeCompletionString and a clean name/signature split. + + S.IsIndexedForCodeCompletion = isIndexedForCodeCompletion(ND, Ctx); + S.SymInfo = index::getSymbolInfo(&ND); + std::string FileURI; + if (auto DeclLoc = getTokenLocation(findNameLoc(&ND), SM, Opts, + ASTCtx->getLangOpts(), FileURI)) + S.CanonicalDeclaration = *DeclLoc; + + // Add completion info. + // FIXME: we may want to choose a different redecl, or combine from several. + assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); + // We use the primary template, as clang does during code completion. + CodeCompletionResult SymbolCompletion(&getTemplateOrThis(ND), 0); + const auto *CCS = SymbolCompletion.CreateCodeCompletionString( + *ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator, + *CompletionTUInfo, + /*IncludeBriefComments*/ false); + std::string Signature; + std::string SnippetSuffix; + getSignature(*CCS, &Signature, &SnippetSuffix); + std::string Documentation = + formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion, + /*CommentsFromHeaders=*/true)); + std::string ReturnType = getReturnType(*CCS); + + std::string Include; + if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) { + // Use the expansion location to get the #include header since this is + // where the symbol is exposed. + if (auto Header = getIncludeHeader( + QName, SM, SM.getExpansionLoc(ND.getLocation()), Opts)) + Include = std::move(*Header); + } + S.Signature = Signature; + S.CompletionSnippetSuffix = SnippetSuffix; + Symbol::Details Detail; + Detail.Documentation = Documentation; + Detail.ReturnType = ReturnType; + Detail.IncludeHeader = Include; + S.Detail = &Detail; + + S.Origin = Opts.Origin; + Symbols.insert(S); + return Symbols.find(S.ID); +} + +void SymbolCollector::addDefinition(const NamedDecl &ND, + const Symbol &DeclSym) { + if (DeclSym.Definition) + return; + // If we saw some forward declaration, we end up copying the symbol. + // This is not ideal, but avoids duplicating the "is this a definition" check + // in clang::index. We should only see one definition. + Symbol S = DeclSym; + std::string FileURI; + if (auto DefLoc = getTokenLocation(findNameLoc(&ND), + ND.getASTContext().getSourceManager(), + Opts, ASTCtx->getLangOpts(), FileURI)) + S.Definition = *DefLoc; + Symbols.insert(S); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/SymbolCollector.h b/clangd/index/SymbolCollector.h new file mode 100644 index 000000000..bc882e47a --- /dev/null +++ b/clangd/index/SymbolCollector.h @@ -0,0 +1,114 @@ +//===--- SymbolCollector.h ---------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CanonicalIncludes.h" +#include "Index.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/Index/IndexDataConsumer.h" +#include "clang/Index/IndexSymbol.h" +#include "clang/Sema/CodeCompleteConsumer.h" + +namespace clang { +namespace clangd { + +/// \brief Collect declarations (symbols) from an AST. +/// It collects most declarations except: +/// - Implicit declarations +/// - Anonymous declarations (anonymous enum/class/struct, etc) +/// - Declarations in anonymous namespaces +/// - Local declarations (in function bodies, blocks, etc) +/// - Declarations in main files +/// - Template specializations +/// - Library-specific private declarations (e.g. private declaration generated +/// by protobuf compiler) +/// +/// See also shouldCollectSymbol(...). +/// +/// Clients (e.g. clangd) can use SymbolCollector together with +/// index::indexTopLevelDecls to retrieve all symbols when the source file is +/// changed. +class SymbolCollector : public index::IndexDataConsumer { +public: + struct Options { + /// When symbol paths cannot be resolved to absolute paths (e.g. files in + /// VFS that does not have absolute path), combine the fallback directory + /// with symbols' paths to get absolute paths. This must be an absolute + /// path. + std::string FallbackDir; + /// Specifies URI schemes that can be used to generate URIs for file paths + /// in symbols. The list of schemes will be tried in order until a working + /// scheme is found. If no scheme works, symbol location will be dropped. + std::vector URISchemes = {"file"}; + bool CollectIncludePath = false; + /// If set, this is used to map symbol #include path to a potentially + /// different #include path. + const CanonicalIncludes *Includes = nullptr; + // Populate the Symbol.References field. + bool CountReferences = false; + // Every symbol collected will be stamped with this origin. + SymbolOrigin Origin = SymbolOrigin::Unknown; + /// Collect macros. + /// Note that SymbolCollector must be run with preprocessor in order to + /// collect macros. For example, `indexTopLevelDecls` will not index any + /// macro even if this is true. + bool CollectMacro = false; + }; + + SymbolCollector(Options Opts); + + /// Returns true is \p ND should be collected. + /// AST matchers require non-const ASTContext. + static bool shouldCollectSymbol(const NamedDecl &ND, ASTContext &ASTCtx, + const Options &Opts); + + void initialize(ASTContext &Ctx) override; + + void setPreprocessor(std::shared_ptr PP) override { + this->PP = std::move(PP); + } + + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override; + + bool handleMacroOccurence(const IdentifierInfo *Name, const MacroInfo *MI, + index::SymbolRoleSet Roles, + SourceLocation Loc) override; + + SymbolSlab takeSymbols() { return std::move(Symbols).build(); } + + void finish() override; + +private: + const Symbol *addDeclaration(const NamedDecl &, SymbolID); + void addDefinition(const NamedDecl &, const Symbol &DeclSymbol); + + // All Symbols collected from the AST. + SymbolSlab::Builder Symbols; + ASTContext *ASTCtx; + std::shared_ptr PP; + std::shared_ptr CompletionAllocator; + std::unique_ptr CompletionTUInfo; + Options Opts; + // Symbols referenced from the current TU, flushed on finish(). + llvm::DenseSet ReferencedDecls; + llvm::DenseSet ReferencedMacros; + // Maps canonical declaration provided by clang to canonical declaration for + // an index symbol, if clangd prefers a different declaration than that + // provided by clang. For example, friend declaration might be considered + // canonical by clang but should not be considered canonical in the index + // unless it's a definition. + llvm::DenseMap CanonicalDecls; +}; + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/SymbolYAML.cpp b/clangd/index/SymbolYAML.cpp new file mode 100644 index 000000000..1701b5a09 --- /dev/null +++ b/clangd/index/SymbolYAML.cpp @@ -0,0 +1,207 @@ +//===--- SymbolYAML.cpp ------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SymbolYAML.h" +#include "Index.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/raw_ostream.h" + +LLVM_YAML_IS_DOCUMENT_LIST_VECTOR(clang::clangd::Symbol) + +namespace llvm { +namespace yaml { + +using clang::clangd::Symbol; +using clang::clangd::SymbolID; +using clang::clangd::SymbolLocation; +using clang::index::SymbolInfo; +using clang::index::SymbolLanguage; +using clang::index::SymbolKind; + +// Helper to (de)serialize the SymbolID. We serialize it as a hex string. +struct NormalizedSymbolID { + NormalizedSymbolID(IO &) {} + NormalizedSymbolID(IO &, const SymbolID& ID) { + llvm::raw_string_ostream OS(HexString); + OS << ID; + } + + SymbolID denormalize(IO&) { + SymbolID ID; + HexString >> ID; + return ID; + } + + std::string HexString; +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, SymbolLocation::Position &Value) { + IO.mapRequired("Line", Value.Line); + IO.mapRequired("Column", Value.Column); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, SymbolLocation &Value) { + IO.mapRequired("FileURI", Value.FileURI); + IO.mapRequired("Start", Value.Start); + IO.mapRequired("End", Value.End); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, SymbolInfo &SymInfo) { + // FIXME: expose other fields? + io.mapRequired("Kind", SymInfo.Kind); + io.mapRequired("Lang", SymInfo.Lang); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &io, Symbol::Details &Detail) { + io.mapOptional("Documentation", Detail.Documentation); + io.mapOptional("ReturnType", Detail.ReturnType); + io.mapOptional("IncludeHeader", Detail.IncludeHeader); + } +}; + +// A YamlIO normalizer for fields of type "const T*" allocated on an arena. +// Normalizes to Optional, so traits should be provided for T. +template struct ArenaPtr { + ArenaPtr(IO &) {} + ArenaPtr(IO &, const T *D) { + if (D) + Opt = *D; + } + + const T *denormalize(IO &IO) { + assert(IO.getContext() && "Expecting an arena (as context) to allocate " + "data for read symbols."); + if (!Opt) + return nullptr; + return new (*static_cast(IO.getContext())) + T(std::move(*Opt)); // Allocate a copy of Opt on the arena. + } + + llvm::Optional Opt; +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, Symbol &Sym) { + MappingNormalization NSymbolID(IO, Sym.ID); + MappingNormalization, const Symbol::Details *> + NDetail(IO, Sym.Detail); + IO.mapRequired("ID", NSymbolID->HexString); + IO.mapRequired("Name", Sym.Name); + IO.mapRequired("Scope", Sym.Scope); + IO.mapRequired("SymInfo", Sym.SymInfo); + IO.mapOptional("CanonicalDeclaration", Sym.CanonicalDeclaration, + SymbolLocation()); + IO.mapOptional("Definition", Sym.Definition, SymbolLocation()); + IO.mapOptional("References", Sym.References, 0u); + IO.mapOptional("IsIndexedForCodeCompletion", Sym.IsIndexedForCodeCompletion, + false); + IO.mapOptional("Signature", Sym.Signature); + IO.mapOptional("CompletionSnippetSuffix", Sym.CompletionSnippetSuffix); + IO.mapOptional("Detail", NDetail->Opt); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, SymbolLanguage &Value) { + IO.enumCase(Value, "C", SymbolLanguage::C); + IO.enumCase(Value, "Cpp", SymbolLanguage::CXX); + IO.enumCase(Value, "ObjC", SymbolLanguage::ObjC); + IO.enumCase(Value, "Swift", SymbolLanguage::Swift); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, SymbolKind &Value) { +#define DEFINE_ENUM(name) IO.enumCase(Value, #name, SymbolKind::name) + + DEFINE_ENUM(Unknown); + DEFINE_ENUM(Function); + DEFINE_ENUM(Module); + DEFINE_ENUM(Namespace); + DEFINE_ENUM(NamespaceAlias); + DEFINE_ENUM(Macro); + DEFINE_ENUM(Enum); + DEFINE_ENUM(Struct); + DEFINE_ENUM(Class); + DEFINE_ENUM(Protocol); + DEFINE_ENUM(Extension); + DEFINE_ENUM(Union); + DEFINE_ENUM(TypeAlias); + DEFINE_ENUM(Function); + DEFINE_ENUM(Variable); + DEFINE_ENUM(Field); + DEFINE_ENUM(EnumConstant); + DEFINE_ENUM(InstanceMethod); + DEFINE_ENUM(ClassMethod); + DEFINE_ENUM(StaticMethod); + DEFINE_ENUM(InstanceProperty); + DEFINE_ENUM(ClassProperty); + DEFINE_ENUM(StaticProperty); + DEFINE_ENUM(Constructor); + DEFINE_ENUM(Destructor); + DEFINE_ENUM(ConversionFunction); + DEFINE_ENUM(Parameter); + DEFINE_ENUM(Using); + +#undef DEFINE_ENUM + } +}; + +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace clangd { + +SymbolSlab symbolsFromYAML(llvm::StringRef YAMLContent) { + // Store data of pointer fields (excl. `StringRef`) like `Detail`. + llvm::BumpPtrAllocator Arena; + llvm::yaml::Input Yin(YAMLContent, &Arena); + std::vector S; + Yin >> S; + + SymbolSlab::Builder Syms; + for (auto &Sym : S) + Syms.insert(Sym); + return std::move(Syms).build(); +} + +Symbol SymbolFromYAML(llvm::yaml::Input &Input, llvm::BumpPtrAllocator &Arena) { + // We could grab Arena out of Input, but it'd be a huge hazard for callers. + assert(Input.getContext() == &Arena); + Symbol S; + Input >> S; + return S; +} + +void SymbolsToYAML(const SymbolSlab& Symbols, llvm::raw_ostream &OS) { + llvm::yaml::Output Yout(OS); + for (Symbol S : Symbols) // copy: Yout<< requires mutability. + Yout << S; +} + +std::string SymbolToYAML(Symbol Sym) { + std::string Str; + llvm::raw_string_ostream OS(Str); + llvm::yaml::Output Yout(OS); + Yout << Sym; + return OS.str(); +} + +} // namespace clangd +} // namespace clang diff --git a/clangd/index/SymbolYAML.h b/clangd/index/SymbolYAML.h new file mode 100644 index 000000000..726af6c66 --- /dev/null +++ b/clangd/index/SymbolYAML.h @@ -0,0 +1,48 @@ +//===--- SymbolYAML.h --------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// SymbolYAML provides facilities to convert Symbol to YAML, and vice versa. +// The YAML format of Symbol is designed for simplicity and experiment, but +// isn't a suitable/efficient store. +// +// This is for **experimental** only. Don't use it in the production code. +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_FROM_YAML_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_FROM_YAML_H + +#include "Index.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { + +// Read symbols from a YAML-format string. +SymbolSlab symbolsFromYAML(llvm::StringRef YAMLContent); + +// Read one symbol from a YAML-stream. +// The arena must be the Input's context! (i.e. yaml::Input Input(Text, &Arena)) +// The returned symbol is backed by both Input and Arena. +Symbol SymbolFromYAML(llvm::yaml::Input &Input, llvm::BumpPtrAllocator &Arena); + +// Convert a single symbol to YAML-format string. +// The YAML result is safe to concatenate. +std::string SymbolToYAML(Symbol Sym); + +// Convert symbols to a YAML-format string. +// The YAML result is safe to concatenate if you have multiple symbol slabs. +void SymbolsToYAML(const SymbolSlab &Symbols, llvm::raw_ostream &OS); + +} // namespace clangd +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_SYMBOL_FROM_YAML_H diff --git a/clangd/index/dex/Iterator.cpp b/clangd/index/dex/Iterator.cpp new file mode 100644 index 000000000..25107f92c --- /dev/null +++ b/clangd/index/dex/Iterator.cpp @@ -0,0 +1,244 @@ +//===--- Iterator.cpp - Query Symbol Retrieval ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Iterator.h" +#include +#include +#include + +namespace clang { +namespace clangd { +namespace dex { + +namespace { + +/// Implements Iterator over a PostingList. DocumentIterator is the most basic +/// iterator: it doesn't have any children (hence it is the leaf of iterator +/// tree) and is simply a wrapper around PostingList::const_iterator. +class DocumentIterator : public Iterator { +public: + DocumentIterator(PostingListRef Documents) + : Documents(Documents), Index(std::begin(Documents)) {} + + bool reachedEnd() const override { return Index == std::end(Documents); } + + /// Advances cursor to the next item. + void advance() override { + assert(!reachedEnd() && "DocumentIterator can't advance at the end."); + ++Index; + } + + /// Applies binary search to advance cursor to the next item with DocID equal + /// or higher than the given one. + void advanceTo(DocID ID) override { + assert(!reachedEnd() && "DocumentIterator can't advance at the end."); + Index = std::lower_bound(Index, std::end(Documents), ID); + } + + DocID peek() const override { + assert(!reachedEnd() && "DocumentIterator can't call peek() at the end."); + return *Index; + } + + llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override { + OS << '['; + auto Separator = ""; + for (const auto &ID : Documents) { + OS << Separator << ID; + Separator = ", "; + } + OS << ']'; + return OS; + } + +private: + PostingListRef Documents; + PostingListRef::const_iterator Index; +}; + +/// Implements Iterator over the intersection of other iterators. +/// +/// AndIterator iterates through common items among all children. It becomes +/// exhausted as soon as any child becomes exhausted. After each mutation, the +/// iterator restores the invariant: all children must point to the same item. +class AndIterator : public Iterator { +public: + AndIterator(std::vector> AllChildren) + : Children(std::move(AllChildren)) { + assert(!Children.empty() && "AndIterator should have at least one child."); + // Establish invariants. + sync(); + } + + bool reachedEnd() const override { return ReachedEnd; } + + /// Advances all children to the next common item. + void advance() override { + assert(!reachedEnd() && "AndIterator can't call advance() at the end."); + Children.front()->advance(); + sync(); + } + + /// Advances all children to the next common item with DocumentID >= ID. + void advanceTo(DocID ID) override { + assert(!reachedEnd() && "AndIterator can't call advanceTo() at the end."); + Children.front()->advanceTo(ID); + sync(); + } + + DocID peek() const override { return Children.front()->peek(); } + + llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override { + OS << "(& "; + auto Separator = ""; + for (const auto &Child : Children) { + OS << Separator << *Child; + Separator = " "; + } + OS << ')'; + return OS; + } + +private: + /// Restores class invariants: each child will point to the same element after + /// sync. + void sync() { + ReachedEnd |= Children.front()->reachedEnd(); + if (ReachedEnd) + return; + auto SyncID = Children.front()->peek(); + // Indicates whether any child needs to be advanced to new SyncID. + bool NeedsAdvance = false; + do { + NeedsAdvance = false; + for (auto &Child : Children) { + Child->advanceTo(SyncID); + ReachedEnd |= Child->reachedEnd(); + // If any child reaches end And iterator can not match any other items. + // In this case, just terminate the process. + if (ReachedEnd) + return; + // If any child goes beyond given ID (i.e. ID is not the common item), + // all children should be advanced to the next common item. + // FIXME(kbobyrev): This is not a very optimized version; after costs + // are introduced, cycle should break whenever ID exceeds current one + // and cheapest children should be advanced over again. + if (Child->peek() > SyncID) { + SyncID = Child->peek(); + NeedsAdvance = true; + } + } + } while (NeedsAdvance); + } + + /// AndIterator owns its children and ensures that all of them point to the + /// same element. As soon as one child gets exhausted, AndIterator can no + /// longer advance and has reached its end. + std::vector> Children; + /// Indicates whether any child is exhausted. It is cheaper to maintain and + /// update the field, rather than traversing the whole subtree in each + /// reachedEnd() call. + bool ReachedEnd = false; +}; + +/// Implements Iterator over the union of other iterators. +/// +/// OrIterator iterates through all items which can be pointed to by at least +/// one child. To preserve the sorted order, this iterator always advances the +/// child with smallest Child->peek() value. OrIterator becomes exhausted as +/// soon as all of its children are exhausted. +class OrIterator : public Iterator { +public: + OrIterator(std::vector> AllChildren) + : Children(std::move(AllChildren)) { + assert(Children.size() > 0 && "Or Iterator must have at least one child."); + } + + /// Returns true if all children are exhausted. + bool reachedEnd() const override { + return std::all_of(begin(Children), end(Children), + [](const std::unique_ptr &Child) { + return Child->reachedEnd(); + }); + } + + /// Moves each child pointing to the smallest DocID to the next item. + void advance() override { + assert(!reachedEnd() && + "OrIterator must have at least one child to advance()."); + const auto SmallestID = peek(); + for (const auto &Child : Children) + if (!Child->reachedEnd() && Child->peek() == SmallestID) + Child->advance(); + } + + /// Advances each child to the next existing element with DocumentID >= ID. + void advanceTo(DocID ID) override { + assert(!reachedEnd() && "Can't advance iterator after it reached the end."); + for (const auto &Child : Children) + if (!Child->reachedEnd()) + Child->advanceTo(ID); + } + + /// Returns the element under cursor of the child with smallest Child->peek() + /// value. + DocID peek() const override { + assert(!reachedEnd() && + "OrIterator must have at least one child to peek()."); + DocID Result = std::numeric_limits::max(); + + for (const auto &Child : Children) + if (!Child->reachedEnd()) + Result = std::min(Result, Child->peek()); + + return Result; + } + + llvm::raw_ostream &dump(llvm::raw_ostream &OS) const override { + OS << "(| "; + auto Separator = ""; + for (const auto &Child : Children) { + OS << Separator << *Child; + Separator = " "; + } + OS << ')'; + return OS; + } + +private: + // FIXME(kbobyrev): Would storing Children in min-heap be faster? + std::vector> Children; +}; + +} // end namespace + +std::vector consume(Iterator &It) { + std::vector Result; + for (; !It.reachedEnd(); It.advance()) + Result.push_back(It.peek()); + return Result; +} + +std::unique_ptr create(PostingListRef Documents) { + return llvm::make_unique(Documents); +} + +std::unique_ptr +createAnd(std::vector> Children) { + return llvm::make_unique(move(Children)); +} + +std::unique_ptr +createOr(std::vector> Children) { + return llvm::make_unique(move(Children)); +} + +} // namespace dex +} // namespace clangd +} // namespace clang diff --git a/clangd/index/dex/Iterator.h b/clangd/index/dex/Iterator.h new file mode 100644 index 000000000..f6270f12f --- /dev/null +++ b/clangd/index/dex/Iterator.h @@ -0,0 +1,152 @@ +//===--- Iterator.h - Query Symbol Retrieval --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Symbol index queries consist of specific requirements for the requested +// symbol, such as high fuzzy matching score, scope, type etc. The lists of all +// symbols matching some criteria (e.g. belonging to "clang::clangd::" scope) +// are expressed in a form of Search Tokens which are stored in the inverted +// index. Inverted index maps these tokens to the posting lists - sorted ( by +// symbol quality) sequences of symbol IDs matching the token, e.g. scope token +// "clangd::clangd::" is mapped to the list of IDs of all symbols which are +// declared in this namespace. Search queries are build from a set of +// requirements which can be combined with each other forming the query trees. +// The leafs of such trees are posting lists, and the nodes are operations on +// these posting lists, e.g. intersection or union. Efficient processing of +// these multi-level queries is handled by Iterators. Iterators advance through +// all leaf posting lists producing the result of search query, which preserves +// the sorted order of IDs. Having the resulting IDs sorted is important, +// because it allows receiving a certain number of the most valuable items (e.g. +// symbols with highest quality which was the sorting key in the first place) +// without processing all items with requested properties (this might not be +// computationally effective if search request is not very restrictive). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_DEX_ITERATOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_DEX_ITERATOR_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace clang { +namespace clangd { +namespace dex { + +/// Symbol position in the list of all index symbols sorted by a pre-computed +/// symbol quality. +using DocID = uint32_t; +/// Contains sorted sequence of DocIDs all of which belong to symbols matching +/// certain criteria, i.e. containing a Search Token. PostingLists are values +/// for the inverted index. +using PostingList = std::vector; +/// Immutable reference to PostingList object. +using PostingListRef = llvm::ArrayRef; + +/// Iterator is the interface for Query Tree node. The simplest type of Iterator +/// is DocumentIterator which is simply a wrapper around PostingList iterator +/// and serves as the Query Tree leaf. More sophisticated examples of iterators +/// can manage intersection, union of the elements produced by other iterators +/// (their children) to form a multi-level Query Tree. The interface is designed +/// to be extensible in order to support multiple types of iterators. +class Iterator { + // FIXME(kbobyrev): Provide callback for matched documents. + // FIXME(kbobyrev): Implement new types of iterators: Label, Boost (with + // scoring), Limit. + // FIXME(kbobyrev): Implement iterator cost, an estimate of advance() calls + // before iterator exhaustion. +public: + /// Returns true if all valid DocIDs were processed and hence the iterator is + /// exhausted. + virtual bool reachedEnd() const = 0; + /// Moves to next valid DocID. If it doesn't exist, the iterator is exhausted + /// and proceeds to the END. + /// + /// Note: reachedEnd() must be false. + virtual void advance() = 0; + /// Moves to the first valid DocID which is equal or higher than given ID. If + /// it doesn't exist, the iterator is exhausted and proceeds to the END. + /// + /// Note: reachedEnd() must be false. + virtual void advanceTo(DocID ID) = 0; + /// Returns the current element this iterator points to. + /// + /// Note: reachedEnd() must be false. + virtual DocID peek() const = 0; + + virtual ~Iterator() {} + + /// Prints a convenient human-readable iterator representation by recursively + /// dumping iterators in the following format: + /// + /// (Type Child1 Child2 ...) + /// + /// Where Type is the iterator type representation: "&" for And, "|" for Or, + /// ChildN is N-th iterator child. Raw iterators over PostingList are + /// represented as "[ID1, ID2, ...]" where IDN is N-th PostingList entry. + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const Iterator &Iterator) { + return Iterator.dump(OS); + } + +private: + virtual llvm::raw_ostream &dump(llvm::raw_ostream &OS) const = 0; +}; + +/// Exhausts given iterator and returns all processed DocIDs. The result +/// contains sorted DocumentIDs. +std::vector consume(Iterator &It); + +/// Returns a document iterator over given PostingList. +std::unique_ptr create(PostingListRef Documents); + +/// Returns AND Iterator which performs the intersection of the PostingLists of +/// its children. +std::unique_ptr +createAnd(std::vector> Children); + +/// Returns OR Iterator which performs the union of the PostingLists of its +/// children. +std::unique_ptr +createOr(std::vector> Children); + +/// This allows createAnd(create(...), create(...)) syntax. +template std::unique_ptr createAnd(Args... args) { + std::vector> Children; + populateChildren(Children, args...); + return createAnd(move(Children)); +} + +/// This allows createOr(create(...), create(...)) syntax. +template std::unique_ptr createOr(Args... args) { + std::vector> Children; + populateChildren(Children, args...); + return createOr(move(Children)); +} + +template +void populateChildren(std::vector> &Children, + HeadT &Head, TailT &... Tail) { + Children.push_back(move(Head)); + populateChildren(Children, Tail...); +} + +template +void populateChildren(std::vector> &Children, + HeadT &Head) { + Children.push_back(move(Head)); +} + +} // namespace dex +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/index/dex/Token.h b/clangd/index/dex/Token.h new file mode 100644 index 000000000..33a16d36a --- /dev/null +++ b/clangd/index/dex/Token.h @@ -0,0 +1,112 @@ +//===--- Token.h - Symbol Search primitive ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Token objects represent a characteristic of a symbol, which can be used to +// perform efficient search. Tokens are keys for inverted index which are mapped +// to the corresponding posting lists. +// +// The symbol std::cout might have the tokens: +// * Scope "std::" +// * Trigram "cou" +// * Trigram "out" +// * Type "std::ostream" +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TOKEN_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TOKEN_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include + +namespace clang { +namespace clangd { +namespace dex { + +/// A Token represents an attribute of a symbol, such as a particular trigram +/// present in the name (used for fuzzy search). +/// +/// Tokens can be used to perform more sophisticated search queries by +/// constructing complex iterator trees. +struct Token { + /// Kind specifies Token type which defines semantics for the internal + /// representation. Each Kind has different representation stored in Data + /// field. + enum class Kind { + /// Represents trigram used for fuzzy search of unqualified symbol names. + /// + /// Data contains 3 bytes with trigram contents. + Trigram, + /// Scope primitives, e.g. "symbol belongs to namespace foo::bar". + /// + /// Data stroes full scope name , e.g. "foo::bar::baz::" or "" (for global + /// scope). + Scope, + /// Internal Token type for invalid/special tokens, e.g. empty tokens for + /// llvm::DenseMap. + Sentinel, + /// FIXME(kbobyrev): Add other Token Kinds + /// * Path with full or relative path to the directory in which symbol is + /// defined + /// * Type with qualified type name or its USR + }; + + Token(Kind TokenKind, llvm::StringRef Data) + : Data(Data), TokenKind(TokenKind) {} + + bool operator==(const Token &Other) const { + return TokenKind == Other.TokenKind && Data == Other.Data; + } + + /// Representation which is unique among Token with the same Kind. + std::string Data; + Kind TokenKind; + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Token &T) { + return OS << T.Data; + } + +private: + friend llvm::hash_code hash_value(const Token &Token) { + return llvm::hash_combine(static_cast(Token.TokenKind), Token.Data); + } +}; + +} // namespace dex +} // namespace clangd +} // namespace clang + +namespace llvm { + +// Support Tokens as DenseMap keys. +template <> struct DenseMapInfo { + static inline clang::clangd::dex::Token getEmptyKey() { + return {clang::clangd::dex::Token::Kind::Sentinel, "EmptyKey"}; + } + + static inline clang::clangd::dex::Token getTombstoneKey() { + return {clang::clangd::dex::Token::Kind::Sentinel, "TombstoneKey"}; + } + + static unsigned getHashValue(const clang::clangd::dex::Token &Tag) { + return hash_value(Tag); + } + + static bool isEqual(const clang::clangd::dex::Token &LHS, + const clang::clangd::dex::Token &RHS) { + return LHS == RHS; + } +}; + +} // namespace llvm + +#endif diff --git a/clangd/index/dex/Trigram.cpp b/clangd/index/dex/Trigram.cpp new file mode 100644 index 000000000..549093df7 --- /dev/null +++ b/clangd/index/dex/Trigram.cpp @@ -0,0 +1,132 @@ +//===--- Trigram.cpp - Trigram generation for Fuzzy Matching ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Trigram.h" +#include "../../FuzzyMatch.h" +#include "Token.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringExtras.h" + +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace clangd { +namespace dex { + +// FIXME(kbobyrev): Deal with short symbol symbol names. A viable approach would +// be generating unigrams and bigrams here, too. This would prevent symbol index +// from applying fuzzy matching on a tremendous number of symbols and allow +// supplementary retrieval for short queries. +// +// Short names (total segment length <3 characters) are currently ignored. +std::vector generateIdentifierTrigrams(llvm::StringRef Identifier) { + // Apply fuzzy matching text segmentation. + std::vector Roles(Identifier.size()); + calculateRoles(Identifier, + llvm::makeMutableArrayRef(Roles.data(), Identifier.size())); + + std::string LowercaseIdentifier = Identifier.lower(); + + // For each character, store indices of the characters to which fuzzy matching + // algorithm can jump. There are 3 possible variants: + // + // * Next Tail - next character from the same segment + // * Next Head - front character of the next segment + // * Skip-1-Next Head - front character of the skip-1-next segment + // + // Next stores tuples of three indices in the presented order, if a variant is + // not available then 0 is stored. + std::vector> Next(LowercaseIdentifier.size()); + unsigned NextTail = 0, NextHead = 0, NextNextHead = 0; + for (int I = LowercaseIdentifier.size() - 1; I >= 0; --I) { + Next[I] = {{NextTail, NextHead, NextNextHead}}; + NextTail = Roles[I] == Tail ? I : 0; + if (Roles[I] == Head) { + NextNextHead = NextHead; + NextHead = I; + } + } + + DenseSet UniqueTrigrams; + std::array Chars; + for (size_t I = 0; I < LowercaseIdentifier.size(); ++I) { + // Skip delimiters. + if (Roles[I] != Head && Roles[I] != Tail) + continue; + for (const unsigned J : Next[I]) { + if (!J) + continue; + for (const unsigned K : Next[J]) { + if (!K) + continue; + Chars = {{LowercaseIdentifier[I], LowercaseIdentifier[J], + LowercaseIdentifier[K], 0}}; + auto Trigram = Token(Token::Kind::Trigram, Chars.data()); + // Push unique trigrams to the result. + if (!UniqueTrigrams.count(Trigram)) { + UniqueTrigrams.insert(Trigram); + } + } + } + } + + std::vector Result; + for (const auto &Trigram : UniqueTrigrams) + Result.push_back(Trigram); + + return Result; +} + +// FIXME(kbobyrev): Similarly, to generateIdentifierTrigrams, this ignores short +// inputs (total segment length <3 characters). +std::vector generateQueryTrigrams(llvm::StringRef Query) { + // Apply fuzzy matching text segmentation. + std::vector Roles(Query.size()); + calculateRoles(Query, llvm::makeMutableArrayRef(Roles.data(), Query.size())); + + std::string LowercaseQuery = Query.lower(); + + DenseSet UniqueTrigrams; + std::deque Chars; + + for (size_t I = 0; I < LowercaseQuery.size(); ++I) { + // If current symbol is delimiter, just skip it. + if (Roles[I] != Head && Roles[I] != Tail) + continue; + + Chars.push_back(LowercaseQuery[I]); + + if (Chars.size() > 3) + Chars.pop_front(); + if (Chars.size() == 3) { + auto Trigram = + Token(Token::Kind::Trigram, std::string(begin(Chars), end(Chars))); + // Push unique trigrams to the result. + if (!UniqueTrigrams.count(Trigram)) { + UniqueTrigrams.insert(Trigram); + } + } + } + + std::vector Result; + for (const auto &Trigram : UniqueTrigrams) + Result.push_back(Trigram); + + return Result; +} + +} // namespace dex +} // namespace clangd +} // namespace clang diff --git a/clangd/index/dex/Trigram.h b/clangd/index/dex/Trigram.h new file mode 100644 index 000000000..0e21c7d13 --- /dev/null +++ b/clangd/index/dex/Trigram.h @@ -0,0 +1,62 @@ +//===--- Trigram.h - Trigram generation for Fuzzy Matching ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Trigrams are attributes of the symbol unqualified name used to effectively +// extract symbols which can be fuzzy-matched given user query from the inverted +// index. To match query with the extracted set of trigrams Q, the set of +// generated trigrams T for identifier (unqualified symbol name) should contain +// all items of Q, i.e. Q ⊆ T. +// +// Trigram sets extracted from unqualified name and from query are different: +// the set of query trigrams only contains consecutive sequences of three +// characters (which is only a subset of all trigrams generated for an +// identifier). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TRIGRAM_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DEX_TRIGRAM_H + +#include "Token.h" + +#include + +namespace clang { +namespace clangd { +namespace dex { + +/// Returns list of unique fuzzy-search trigrams from unqualified symbol. +/// +/// First, given Identifier (unqualified symbol name) is segmented using +/// FuzzyMatch API and lowercased. After segmentation, the following technique +/// is applied for generating trigrams: for each letter or digit in the input +/// string the algorithms looks for the possible next and skip-1-next symbols +/// which can be jumped to during fuzzy matching. Each combination of such three +/// symbols is inserted into the result. +/// +/// Trigrams can start at any character in the input. Then we can choose to move +/// to the next character, move to the start of the next segment, or skip over a +/// segment. +/// +/// Note: the returned list of trigrams does not have duplicates, if any trigram +/// belongs to more than one class it is only inserted once. +std::vector generateIdentifierTrigrams(llvm::StringRef Identifier); + +/// Returns list of unique fuzzy-search trigrams given a query. +/// +/// Query is segmented using FuzzyMatch API and downcasted to lowercase. Then, +/// the simplest trigrams - sequences of three consecutive letters and digits +/// are extracted and returned after deduplication. +std::vector generateQueryTrigrams(llvm::StringRef Query); + +} // namespace dex +} // namespace clangd +} // namespace clang + +#endif diff --git a/clangd/tool/CMakeLists.txt b/clangd/tool/CMakeLists.txt new file mode 100644 index 000000000..c18876179 --- /dev/null +++ b/clangd/tool/CMakeLists.txt @@ -0,0 +1,20 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_tool(clangd + ClangdMain.cpp + ) + +set(LLVM_LINK_COMPONENTS + support + ) + +target_link_libraries(clangd + PRIVATE + 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..95ffdfad5 --- /dev/null +++ b/clangd/tool/ClangdMain.cpp @@ -0,0 +1,298 @@ +//===--- 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 "Path.h" +#include "Trace.h" +#include "index/SymbolYAML.h" +#include "clang/Basic/Version.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include + +using namespace clang; +using namespace clang::clangd; + +namespace { +enum class PCHStorageFlag { Disk, Memory }; + +// Build an in-memory static index for global symbols from a YAML-format file. +// The size of global symbols should be relatively small, so that all symbols +// can be managed in memory. +std::unique_ptr buildStaticIndex(llvm::StringRef YamlSymbolFile) { + auto Buffer = llvm::MemoryBuffer::getFile(YamlSymbolFile); + if (!Buffer) { + llvm::errs() << "Can't open " << YamlSymbolFile << "\n"; + return nullptr; + } + auto Slab = symbolsFromYAML(Buffer.get()->getBuffer()); + SymbolSlab::Builder SymsBuilder; + for (auto Sym : Slab) + SymsBuilder.insert(Sym); + + return MemIndex::build(std::move(SymsBuilder).build()); +} +} // namespace + +static llvm::cl::opt CompileCommandsDir( + "compile-commands-dir", + llvm::cl::desc("Specify a path to look for compile_commands.json. If path " + "is invalid, clangd will look in the current directory and " + "parent paths of each source file.")); + +static llvm::cl::opt + WorkerThreadsCount("j", + llvm::cl::desc("Number of async workers used by clangd"), + llvm::cl::init(getDefaultAsyncThreadsCount())); + +// FIXME: also support "plain" style where signatures are always omitted. +enum CompletionStyleFlag { + Detailed, + Bundled, +}; +static llvm::cl::opt CompletionStyle( + "completion-style", + llvm::cl::desc("Granularity of code completion suggestions"), + llvm::cl::values( + clEnumValN(Detailed, "detailed", + "One completion item for each semantically distinct " + "completion, with full type information."), + clEnumValN(Bundled, "bundled", + "Similar completion items (e.g. function overloads) are " + "combined. Type information shown where possible.")), + llvm::cl::init(Detailed)); + +// FIXME: Flags are the wrong mechanism for user preferences. +// We should probably read a dotfile or similar. +static llvm::cl::opt IncludeIneligibleResults( + "include-ineligible-results", + llvm::cl::desc( + "Include ineligible completion results (e.g. private members)"), + llvm::cl::init(clangd::CodeCompleteOptions().IncludeIneligibleResults), + llvm::cl::Hidden); + +static llvm::cl::opt InputStyle( + "input-style", llvm::cl::desc("Input JSON stream encoding"), + llvm::cl::values( + clEnumValN(JSONStreamStyle::Standard, "standard", "usual LSP protocol"), + clEnumValN(JSONStreamStyle::Delimited, "delimited", + "messages delimited by --- lines, with # comment support")), + llvm::cl::init(JSONStreamStyle::Standard)); + +static llvm::cl::opt + PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), + llvm::cl::init(false)); + +static llvm::cl::opt LogLevel( + "log", llvm::cl::desc("Verbosity of log messages written to stderr"), + llvm::cl::values(clEnumValN(Logger::Error, "error", "Error messages only"), + clEnumValN(Logger::Info, "info", + "High level execution tracing"), + clEnumValN(Logger::Debug, "verbose", "Low level details")), + llvm::cl::init(Logger::Info)); + +static llvm::cl::opt Test( + "lit-test", + llvm::cl::desc( + "Abbreviation for -input-style=delimited -pretty -run-synchronously. " + "Intended to simplify lit tests."), + llvm::cl::init(false), llvm::cl::Hidden); + +static llvm::cl::opt PCHStorage( + "pch-storage", + llvm::cl::desc("Storing PCHs in memory increases memory usages, but may " + "improve performance"), + llvm::cl::values( + clEnumValN(PCHStorageFlag::Disk, "disk", "store PCHs on disk"), + clEnumValN(PCHStorageFlag::Memory, "memory", "store PCHs in memory")), + llvm::cl::init(PCHStorageFlag::Disk)); + +static llvm::cl::opt LimitResults( + "limit-results", + llvm::cl::desc("Limit the number of results returned by clangd. " + "0 means no limit."), + llvm::cl::init(100)); + +static llvm::cl::opt RunSynchronously( + "run-synchronously", + llvm::cl::desc("Parse on main thread. If set, -j is ignored"), + llvm::cl::init(false), llvm::cl::Hidden); + +static llvm::cl::opt + ResourceDir("resource-dir", + llvm::cl::desc("Directory for system clang headers"), + llvm::cl::init(""), llvm::cl::Hidden); + +static llvm::cl::opt InputMirrorFile( + "input-mirror-file", + llvm::cl::desc( + "Mirror all LSP input to the specified file. Useful for debugging."), + llvm::cl::init(""), llvm::cl::Hidden); + +static llvm::cl::opt EnableIndex( + "index", + llvm::cl::desc("Enable index-based features such as global code completion " + "and searching for symbols." + "Clang uses an index built from symbols in opened files"), + llvm::cl::init(true)); + +static llvm::cl::opt + ShowOrigins("debug-origin", + llvm::cl::desc("Show origins of completion items"), + llvm::cl::init(clangd::CodeCompleteOptions().ShowOrigins), + llvm::cl::Hidden); + +static llvm::cl::opt HeaderInsertionDecorators( + "header-insertion-decorators", + llvm::cl::desc("Prepend a circular dot or space before the completion " + "label, depending on wether " + "an include line will be inserted or not."), + llvm::cl::init(true)); + +static llvm::cl::opt YamlSymbolFile( + "yaml-symbol-file", + llvm::cl::desc( + "YAML-format global symbol file to build the static index. Clangd will " + "use the static index for global code completion.\n" + "WARNING: This option is experimental only, and will be removed " + "eventually. Don't rely on it."), + llvm::cl::init(""), llvm::cl::Hidden); + +int main(int argc, char *argv[]) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + llvm::cl::SetVersionPrinter([](llvm::raw_ostream &OS) { + OS << clang::getClangToolFullVersion("clangd") << "\n"; + }); + llvm::cl::ParseCommandLineOptions( + argc, argv, + "clangd is a language server that provides IDE-like features to editors. " + "\n\nIt should be used via an editor plugin rather than invoked directly." + "For more information, see:" + "\n\thttps://clang.llvm.org/extra/clangd.html" + "\n\thttps://microsoft.github.io/language-server-protocol/"); + if (Test) { + RunSynchronously = true; + InputStyle = JSONStreamStyle::Delimited; + PrettyPrint = true; + } + + if (!RunSynchronously && WorkerThreadsCount == 0) { + llvm::errs() << "A number of worker threads cannot be 0. Did you mean to " + "specify -run-synchronously?"; + return 1; + } + + if (RunSynchronously) { + if (WorkerThreadsCount.getNumOccurrences()) + llvm::errs() << "Ignoring -j because -run-synchronously is set.\n"; + WorkerThreadsCount = 0; + } + + // Validate command line arguments. + llvm::Optional InputMirrorStream; + if (!InputMirrorFile.empty()) { + std::error_code EC; + InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC, + llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write); + if (EC) { + InputMirrorStream.reset(); + llvm::errs() << "Error while opening an input mirror file: " + << EC.message(); + } + } + + // Setup tracing facilities if CLANGD_TRACE is set. In practice enabling a + // trace flag in your editor's config is annoying, launching with + // `CLANGD_TRACE=trace.json vim` is easier. + llvm::Optional TraceStream; + std::unique_ptr Tracer; + if (auto *TraceFile = getenv("CLANGD_TRACE")) { + std::error_code EC; + TraceStream.emplace(TraceFile, /*ref*/ EC, + llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write); + if (EC) { + TraceStream.reset(); + llvm::errs() << "Error while opening trace file " << TraceFile << ": " + << EC.message(); + } else { + Tracer = trace::createJSONTracer(*TraceStream, PrettyPrint); + } + } + + llvm::Optional TracingSession; + if (Tracer) + TracingSession.emplace(*Tracer); + + JSONOutput Out(llvm::outs(), llvm::errs(), LogLevel, + InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, + PrettyPrint); + + clangd::LoggingSession LoggingSession(Out); + + // If --compile-commands-dir arg was invoked, check value and override default + // path. + llvm::Optional CompileCommandsDirPath; + if (CompileCommandsDir.empty()) { + CompileCommandsDirPath = llvm::None; + } else if (!llvm::sys::path::is_absolute(CompileCommandsDir) || + !llvm::sys::fs::exists(CompileCommandsDir)) { + llvm::errs() << "Path specified by --compile-commands-dir either does not " + "exist or is not an absolute " + "path. The argument will be ignored.\n"; + CompileCommandsDirPath = llvm::None; + } else { + CompileCommandsDirPath = CompileCommandsDir; + } + + ClangdServer::Options Opts; + switch (PCHStorage) { + case PCHStorageFlag::Memory: + Opts.StorePreamblesInMemory = true; + break; + case PCHStorageFlag::Disk: + Opts.StorePreamblesInMemory = false; + break; + } + if (!ResourceDir.empty()) + Opts.ResourceDir = ResourceDir; + Opts.BuildDynamicSymbolIndex = EnableIndex; + std::unique_ptr StaticIdx; + if (EnableIndex && !YamlSymbolFile.empty()) { + StaticIdx = buildStaticIndex(YamlSymbolFile); + Opts.StaticIndex = StaticIdx.get(); + } + Opts.AsyncThreadsCount = WorkerThreadsCount; + + clangd::CodeCompleteOptions CCOpts; + CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; + CCOpts.Limit = LimitResults; + CCOpts.BundleOverloads = CompletionStyle != Detailed; + CCOpts.ShowOrigins = ShowOrigins; + if (!HeaderInsertionDecorators) { + CCOpts.IncludeIndicator.Insert.clear(); + CCOpts.IncludeIndicator.NoInsert.clear(); + } + + // Initialize and run ClangdLSPServer. + ClangdLSPServer LSPServer(Out, CCOpts, CompileCommandsDirPath, Opts); + constexpr int NoShutdownRequestErrorCode = 1; + llvm::set_thread_name("clangd.main"); + // Change stdin to binary to not lose \r\n on windows. + llvm::sys::ChangeStdinToBinary(); + return LSPServer.run(stdin, InputStyle) ? 0 : NoShutdownRequestErrorCode; +} 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/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..157db97c6 --- /dev/null +++ b/docs/ReleaseNotes.rst @@ -0,0 +1,245 @@ +===================================== +Extra Clang Tools 7.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 7.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 7.0.0? +====================================== + +Some of the major new features and improvements to Extra Clang Tools are listed +here. Generic improvements to Extra Clang Tools as a whole or to its underlying +infrastructure are described first, followed by tool-specific sections. + +Improvements to clang-tidy +-------------------------- + +- The checks profiling info can now be stored as JSON files for futher + post-processing and analysis. + +- New module `abseil` for checks related to the `Abseil `_ + library. + +- New module ``portability``. + +- New module ``zircon`` for checks related to Fuchsia's Zircon kernel. + +- New :doc:`abseil-string-find-startswith + ` check. + + Checks whether a ``std::string::find()`` result is compared with 0, and + suggests replacing with ``absl::StartsWith()``. + +- New :doc:`android-comparison-in-temp-failure-retry + ` check. + + Diagnoses comparisons that appear to be incorrectly placed in the argument to + the ``TEMP_FAILURE_RETRY`` macro. + +- New :doc:`bugprone-exception-escape + ` check + + Finds functions which may throw an exception directly or indirectly, but they + should not. + +- New :doc:`bugprone-parent-virtual-call + ` check. + + Detects and fixes calls to grand-...parent virtual methods instead of calls + to overridden parent's virtual methods. + +- New :doc:`bugprone-terminating-continue + ` check + + Checks if a ``continue`` statement terminates the loop. + +- New :doc:`bugprone-throw-keyword-missing + ` check. + + Diagnoses when a temporary object that appears to be an exception is + constructed but not thrown. + +- New :doc:`bugprone-unused-return-value + ` check. + + Warns on unused function return values. + +- New :doc:`cert-msc32-c + ` check + + Detects inappropriate seeding of ``srand()`` function. + +- New :doc:`cert-msc51-cpp + ` check + + Detects inappropriate seeding of C++ random generators and C ``srand()`` function. + +- New :doc:`cppcoreguidelines-avoid-goto + ` check. + + The usage of ``goto`` for control flow is error prone and should be replaced + with looping constructs. Every backward jump is rejected. Forward jumps are + only allowed in nested loops. + +- New :doc:`cppcoreguidelines-narrowing-conversions + ` check + + Checks for narrowing conversions, e.g. ``int i = 0; i += 0.1;``. + +- New :doc:`fuchsia-multiple-inheritance + ` check. + + Warns if a class inherits from multiple classes that are not pure virtual. + +- New `fuchsia-restrict-system-includes + `_ check + + Checks for allowed system includes and suggests removal of any others. + +- New `fuchsia-statically-constructed-objects + `_ check + + Warns if global, non-trivial objects with static storage are constructed, + unless the object is statically initialized with a ``constexpr`` constructor + or has no explicit constructor. + +- New :doc:`fuchsia-trailing-return + ` check. + + Functions that have trailing returns are disallowed, except for those + using ``decltype`` specifiers and lambda with otherwise unutterable + return types. + +- New :doc:`hicpp-multiway-paths-covered + ` check. + + Checks on ``switch`` and ``if`` - ``else if`` constructs that do not cover all possible code paths. + +- New :doc:`modernize-use-uncaught-exceptions + ` check. + + Finds and replaces deprecated uses of ``std::uncaught_exception`` to + ``std::uncaught_exceptions``. + +- New :doc:`portability-simd-intrinsics + ` check. + + Warns or suggests alternatives if SIMD intrinsics are used which can be replaced by + ``std::experimental::simd`` operations. + +- New :doc:`readability-simplify-subscript-expr + ` check. + + Simplifies subscript expressions like ``s.data()[i]`` into ``s[i]``. + +- New :doc:`zircon-temporary-objects + ` check. + + Warns on construction of specific temporary objects in the Zircon kernel. + +- Added the missing bitwise assignment operations to + :doc:`hicpp-signed-bitwise `. + +- New option `MinTypeNameLength` for :doc:`modernize-use-auto + ` check to limit the minimal length of + type names to be replaced with ``auto``. Use to skip replacing short type + names like ``int``/``bool`` with ``auto``. Default value is 5 which means + replace types with the name length >= 5 letters only (ex. ``double``, + ``unsigned``). + +- Add `VariableThreshold` option to :doc:`readability-function-size + ` check. + + Flags functions that have more than a specified number of variables declared + in the body. + +- The `AnalyzeTemporaryDtors` option was removed, since the corresponding + `cfg-temporary-dtors` option of the Static Analyzer now defaults to `true`. + +- New alias :doc:`fuchsia-header-anon-namespaces + ` to :doc:`google-build-namespaces + ` + added. + +- New alias :doc:`hicpp-avoid-goto + ` to :doc:`cppcoreguidelines-avoid-goto + ` + added. + +- Removed the `google-readability-redundant-smartptr-get` alias of the + :doc:`readability-redundant-smartptr-get + ` check. + +- The 'misc-forwarding-reference-overload' check was renamed to :doc:`bugprone-forwarding-reference-overload + ` + +- The 'misc-incorrect-roundings' check was renamed to :doc:`bugprone-incorrect-roundings + ` + +- The 'misc-lambda-function-name' check was renamed to :doc:`bugprone-lambda-function-name + ` + +- The 'misc-macro-parentheses' check was renamed to :doc:`bugprone-macro-parentheses + ` + +- The 'misc-macro-repeated-side-effects' check was renamed to :doc:`bugprone-macro-repeated-side-effects + ` + +- The 'misc-misplaced-widening-cast' check was renamed to :doc:`bugprone-misplaced-widening-cast + ` + +- The 'misc-sizeof-container' check was renamed to :doc:`bugprone-sizeof-container + ` + +- The 'misc-sizeof-expression' check was renamed to :doc:`bugprone-sizeof-expression + ` + +- The 'misc-string-compare' check was renamed to :doc:`readability-string-compare + ` + +- The 'misc-string-integer-assignment' check was renamed to :doc:`bugprone-string-integer-assignment + ` + +- The 'misc-string-literal-with-embedded-nul' check was renamed to :doc:`bugprone-string-literal-with-embedded-nul + ` + +- The 'misc-suspicious-enum-usage' check was renamed to :doc:`bugprone-suspicious-enum-usage + ` + +- The 'misc-suspicious-missing-comma' check was renamed to :doc:`bugprone-suspicious-missing-comma + ` + +- The 'misc-suspicious-semicolon' check was renamed to :doc:`bugprone-suspicious-semicolon + ` + +- The 'misc-suspicious-string-compare' check was renamed to :doc:`bugprone-suspicious-string-compare + ` + +- The 'misc-swapped-arguments' check was renamed to :doc:`bugprone-swapped-arguments + ` + +- The 'misc-undelegated-constructor' check was renamed to :doc:`bugprone-undelegated-constructor + ` + +- The 'misc-unused-raii' check was renamed to :doc:`bugprone-unused-raii + ` + +- The 'google-runtime-member-string-references' check was removed. diff --git a/docs/clang-doc.rst b/docs/clang-doc.rst new file mode 100644 index 000000000..f891b71ab --- /dev/null +++ b/docs/clang-doc.rst @@ -0,0 +1,65 @@ +=================== +Clang-Doc +=================== + +.. contents:: + +.. toctree:: + :maxdepth: 1 + +:program:`clang-doc` is a tool for generating C and C++ documenation from +source code and comments. + +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, please to put a feature request +there. + +Use +===== + +:program:`clang-doc` is a `LibTooling +`_-based tool, and so requires a +compile command database for your project (for an example of how to do this +see `How To Setup Tooling For LLVM +`_). + +The tool can be used on a single file or multiple files as defined in +the compile commands database: + +.. code-block:: console + + $ clang-doc /path/to/file.cpp -p /path/to/compile/commands + +This generates an intermediate representation of the declarations and their +associated information in the specified TUs, serialized to LLVM bitcode. + +As currently implemented, the tool is only able to parse TUs that can be +stored in-memory. Future additions will extend the current framework to use +map-reduce frameworks to allow for use with large codebases. + +:program:`clang-doc` offers the following options: + +.. code-block:: console + + $ clang-doc --help + USAGE: clang-doc [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-doc options: + + -doxygen - Use only doxygen-style comments to generate docs. + -dump - Dump intermediate results to bitcode file. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -omit-filenames - Omit filenames in output. + -output= - Directory for outputting generated files. + -p= - Build path 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/abseil-string-find-startswith.rst b/docs/clang-tidy/checks/abseil-string-find-startswith.rst new file mode 100644 index 000000000..43f35ac66 --- /dev/null +++ b/docs/clang-tidy/checks/abseil-string-find-startswith.rst @@ -0,0 +1,41 @@ +.. title:: clang-tidy - abseil-string-find-startswith + +abseil-string-find-startswith +============================= + +Checks whether a ``std::string::find()`` result is compared with 0, and +suggests replacing with ``absl::StartsWith()``. This is both a readability and +performance issue. + +.. code-block:: c++ + + string s = "..."; + if (s.find("Hello World") == 0) { /* do something */ } + +becomes + + +.. code-block:: c++ + + string s = "..."; + if (absl::StartsWith(s, "Hello World")) { /* do something */ } + + +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 considered is + fixed. + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. + +.. option:: AbseilStringsMatchHeader + + The location of Abseil's ``strings/match.h``. Defaults to + ``absl/strings/match.h``. diff --git a/docs/clang-tidy/checks/android-cloexec-accept.rst b/docs/clang-tidy/checks/android-cloexec-accept.rst new file mode 100644 index 000000000..58ce05f3e --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-accept.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-accept + +android-cloexec-accept +====================== + +The usage of ``accept()`` is not recommended, it's better to use ``accept4()``. +Without this flag, an opened sensitive file descriptor would remain open across +a fork+exec to a lower-privileged SELinux domain. + +Examples: + +.. code-block:: c++ + + accept(sockfd, addr, addrlen); + + // becomes + + accept4(sockfd, addr, addrlen, SOCK_CLOEXEC); diff --git a/docs/clang-tidy/checks/android-cloexec-accept4.rst b/docs/clang-tidy/checks/android-cloexec-accept4.rst new file mode 100644 index 000000000..1633395d8 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-accept4.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-accept4 + +android-cloexec-accept4 +======================= + +``accept4()`` 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++ + + accept4(sockfd, addr, addrlen, SOCK_NONBLOCK); + + // becomes + + accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); 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-dup.rst b/docs/clang-tidy/checks/android-cloexec-dup.rst new file mode 100644 index 000000000..d287a657d --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-dup.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-dup + +android-cloexec-dup +=================== + +The usage of ``dup()`` is not recommended, it's better to use ``fcntl()``, +which can set the close-on-exec flag. Otherwise, an opened sensitive file would +remain open across a fork+exec to a lower-privileged SELinux domain. + +Examples: + +.. code-block:: c++ + + int fd = dup(oldfd); + + // becomes + + int fd = fcntl(oldfd, F_DUPFD_CLOEXEC); diff --git a/docs/clang-tidy/checks/android-cloexec-epoll-create.rst b/docs/clang-tidy/checks/android-cloexec-epoll-create.rst new file mode 100644 index 000000000..98f0f6c90 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-epoll-create.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - android-cloexec-epoll-create + +android-cloexec-epoll-create +============================ + +The usage of ``epoll_create()`` is not recommended, it's better to use +``epoll_create1()``, which allows close-on-exec. + +Examples: + +.. code-block:: c++ + + epoll_create(size); + + // becomes + + epoll_create1(EPOLL_CLOEXEC); diff --git a/docs/clang-tidy/checks/android-cloexec-epoll-create1.rst b/docs/clang-tidy/checks/android-cloexec-epoll-create1.rst new file mode 100644 index 000000000..7e7c4adb0 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-epoll-create1.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-epoll-create1 + +android-cloexec-epoll-create1 +============================= + +``epoll_create1()`` should include ``EPOLL_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++ + + epoll_create1(0); + + // becomes + + epoll_create1(EPOLL_CLOEXEC); 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-inotify-init.rst b/docs/clang-tidy/checks/android-cloexec-inotify-init.rst new file mode 100644 index 000000000..cee4c7bf4 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-inotify-init.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - android-cloexec-inotify-init + +android-cloexec-inotify-init +============================ + +The usage of ``inotify_init()`` is not recommended, it's better to use +``inotify_init1()``. + +Examples: + +.. code-block:: c++ + + inotify_init(); + + // becomes + + inotify_init1(IN_CLOEXEC); diff --git a/docs/clang-tidy/checks/android-cloexec-inotify-init1.rst b/docs/clang-tidy/checks/android-cloexec-inotify-init1.rst new file mode 100644 index 000000000..827598ca7 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-inotify-init1.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-inotify-init1 + +android-cloexec-inotify-init1 +============================= + +``inotify_init1()`` should include ``IN_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++ + + inotify_init1(IN_NONBLOCK); + + // becomes + + inotify_init1(IN_NONBLOCK | IN_CLOEXEC); diff --git a/docs/clang-tidy/checks/android-cloexec-memfd-create.rst b/docs/clang-tidy/checks/android-cloexec-memfd-create.rst new file mode 100644 index 000000000..a45321d64 --- /dev/null +++ b/docs/clang-tidy/checks/android-cloexec-memfd-create.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - android-cloexec-memfd-create + +android-cloexec-memfd-create +============================ + +``memfd_create()`` should include ``MFD_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++ + + memfd_create(name, MFD_ALLOW_SEALING); + + // becomes + + memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC); 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/android-comparison-in-temp-failure-retry.rst b/docs/clang-tidy/checks/android-comparison-in-temp-failure-retry.rst new file mode 100644 index 000000000..e4de4b043 --- /dev/null +++ b/docs/clang-tidy/checks/android-comparison-in-temp-failure-retry.rst @@ -0,0 +1,36 @@ +.. title:: clang-tidy - android-comparison-in-temp-failure-retry + +android-comparison-in-temp-failure-retry +======================================== + +Diagnoses comparisons that appear to be incorrectly placed in the argument to +the ``TEMP_FAILURE_RETRY`` macro. Having such a use is incorrect in the vast +majority of cases, and will often silently defeat the purpose of the +``TEMP_FAILURE_RETRY`` macro. + +For context, ``TEMP_FAILURE_RETRY`` is `a convenience macro +`_ +provided by both glibc and Bionic. Its purpose is to repeatedly run a syscall +until it either succeeds, or fails for reasons other than being interrupted. + +Example buggy usage looks like: + +.. code-block:: c + + char cs[1]; + while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs)) != 0)) { + // Do something with cs. + } + +Because TEMP_FAILURE_RETRY will check for whether the result *of the comparison* +is ``-1``, and retry if so. + +If you encounter this, the fix is simple: lift the comparison out of the +``TEMP_FAILURE_RETRY`` argument, like so: + +.. code-block:: c + + char cs[1]; + while (TEMP_FAILURE_RETRY(read(STDIN_FILENO, cs, sizeof(cs))) != 0) { + // Do something with cs. + } 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-argument-comment.rst b/docs/clang-tidy/checks/bugprone-argument-comment.rst new file mode 100644 index 000000000..5f7e5f0d5 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-argument-comment.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - bugprone-argument-comment + +bugprone-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/bugprone-assert-side-effect.rst b/docs/clang-tidy/checks/bugprone-assert-side-effect.rst new file mode 100644 index 000000000..dc7a3c9a4 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-assert-side-effect.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - bugprone-assert-side-effect + +bugprone-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/bugprone-bool-pointer-implicit-conversion.rst b/docs/clang-tidy/checks/bugprone-bool-pointer-implicit-conversion.rst new file mode 100644 index 000000000..e42ee5f60 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-bool-pointer-implicit-conversion.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - bugprone-bool-pointer-implicit-conversion + +bugprone-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/bugprone-copy-constructor-init.rst b/docs/clang-tidy/checks/bugprone-copy-constructor-init.rst new file mode 100644 index 000000000..a2b39bc4f --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-copy-constructor-init.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - bugprone-copy-constructor-init + +bugprone-copy-constructor-init +============================== + +Finds copy constructors where the constructor doesn't call +the copy constructor of the base class. + +.. code-block:: c++ + + class Copyable { + public: + Copyable() = default; + Copyable(const Copyable &) = default; + }; + class X2 : public Copyable { + X2(const X2 &other) {} // Copyable(other) is missing + }; + +Also finds copy constructors where the constructor of +the base class don't have parameter. + +.. code-block:: c++ + + class X4 : public Copyable { + X4(const X4 &other) : Copyable() {} // other is missing + }; + +The check also suggests a fix-its in some cases. diff --git a/docs/clang-tidy/checks/bugprone-dangling-handle.rst b/docs/clang-tidy/checks/bugprone-dangling-handle.rst new file mode 100644 index 000000000..8c2c316c0 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-dangling-handle.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - bugprone-dangling-handle + +bugprone-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/bugprone-exception-escape.rst b/docs/clang-tidy/checks/bugprone-exception-escape.rst new file mode 100644 index 000000000..e9653a7e5 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-exception-escape.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - bugprone-exception-escape + +bugprone-exception-escape +========================= + +Finds functions which may throw an exception directly or indirectly, but they +should not. The functions which should not throw exceptions are the following: +* Destructors +* Move constructors +* Move assignment operators +* The ``main()`` functions +* ``swap()`` functions +* Functions marked with ``throw()`` or ``noexcept`` +* Other functions given as option + +A destructor throwing an exception may result in undefined behavior, resource +leaks or unexpected termination of the program. Throwing move constructor or +move assignment also may result in undefined behavior or resource leak. The +``swap()`` operations expected to be non throwing most of the cases and they +are always possible to implement in a non throwing way. Non throwing ``swap()`` +operations are also used to create move operations. A throwing ``main()`` +function also results in unexpected termination. + +WARNING! This check may be expensive on large source files. + +Options +------- + +.. option:: FunctionsThatShouldNotThrow + + Comma separated list containing function names which should not throw. An + example value for this parameter can be ``WinMain`` which adds function + ``WinMain()`` in the Windows API to the list of the funcions which should + not throw. Default value is an empty string. + +.. option:: IgnoredExceptions + + Comma separated list containing type names which are not counted as thrown + exceptions in the check. Default value is an empty string. diff --git a/docs/clang-tidy/checks/bugprone-fold-init-type.rst b/docs/clang-tidy/checks/bugprone-fold-init-type.rst new file mode 100644 index 000000000..ebe3d22c0 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-fold-init-type.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - bugprone-fold-init-type + +bugprone-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/bugprone-forward-declaration-namespace.rst b/docs/clang-tidy/checks/bugprone-forward-declaration-namespace.rst new file mode 100644 index 000000000..99ecb63e7 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-forward-declaration-namespace.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - bugprone-forward-declaration-namespace + +bugprone-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/bugprone-forwarding-reference-overload.rst b/docs/clang-tidy/checks/bugprone-forwarding-reference-overload.rst new file mode 100644 index 000000000..ddf69dca9 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-forwarding-reference-overload.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - bugprone-forwarding-reference-overload + +bugprone-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/bugprone-inaccurate-erase.rst b/docs/clang-tidy/checks/bugprone-inaccurate-erase.rst new file mode 100644 index 000000000..7df47b03e --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-inaccurate-erase.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - bugprone-inaccurate-erase + +bugprone-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/bugprone-incorrect-roundings.rst b/docs/clang-tidy/checks/bugprone-incorrect-roundings.rst new file mode 100644 index 000000000..0a5047a9f --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-incorrect-roundings.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - bugprone-incorrect-roundings + +bugprone-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/bugprone-integer-division.rst b/docs/clang-tidy/checks/bugprone-integer-division.rst new file mode 100644 index 000000000..2c82e6fa1 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-integer-division.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - bugprone-integer-division + +bugprone-integer-division +========================= + +Finds cases where integer division in a floating point context is likely to +cause unintended loss of precision. + +No reports are made if divisions are part of the following expressions: + +- operands of operators expecting integral or bool types, +- call expressions of integral or bool types, and +- explicit cast expressions to integral or bool types, + +as these are interpreted as signs of deliberateness from the programmer. + +Examples: + +.. code-block:: c++ + + float floatFunc(float); + int intFunc(int); + double d; + int i = 42; + + // Warn, floating-point values expected. + d = 32 * 8 / (2 + i); + d = 8 * floatFunc(1 + 7 / 2); + d = i / (1 << 4); + + // OK, no integer division. + d = 32 * 8.0 / (2 + i); + d = 8 * floatFunc(1 + 7.0 / 2); + d = (double)i / (1 << 4); + + // OK, there are signs of deliberateness. + d = 1 << (i / 2); + d = 9 + intFunc(6 * i / 32); + d = (int)(i / 32) - 8; diff --git a/docs/clang-tidy/checks/bugprone-lambda-function-name.rst b/docs/clang-tidy/checks/bugprone-lambda-function-name.rst new file mode 100644 index 000000000..683977a3d --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-lambda-function-name.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - bugprone-lambda-function-name + +bugprone-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/bugprone-macro-parentheses.rst b/docs/clang-tidy/checks/bugprone-macro-parentheses.rst new file mode 100644 index 000000000..f8270801c --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-macro-parentheses.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - bugprone-macro-parentheses + +bugprone-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/bugprone-macro-repeated-side-effects.rst b/docs/clang-tidy/checks/bugprone-macro-repeated-side-effects.rst new file mode 100644 index 000000000..15dfdcaf6 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-macro-repeated-side-effects.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - bugprone-macro-repeated-side-effects + +bugprone-macro-repeated-side-effects +==================================== + + +Checks for repeated argument with side effects in macros. diff --git a/docs/clang-tidy/checks/bugprone-misplaced-operator-in-strlen-in-alloc.rst b/docs/clang-tidy/checks/bugprone-misplaced-operator-in-strlen-in-alloc.rst new file mode 100644 index 000000000..e6ff2fe10 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-misplaced-operator-in-strlen-in-alloc.rst @@ -0,0 +1,57 @@ +.. title:: clang-tidy - bugprone-misplaced-operator-in-strlen-in-alloc + +bugprone-misplaced-operator-in-strlen-in-alloc +============================================== + +Finds cases where ``1`` is added to the string in the argument to ``strlen()``, +``strnlen()``, ``strnlen_s()``, ``wcslen()``, ``wcsnlen()``, and ``wcsnlen_s()`` +instead of the result and the value is used as an argument to a memory +allocation function (``malloc()``, ``calloc()``, ``realloc()``, ``alloca()``) or +the ``new[]`` operator in `C++`. The check detects error cases even if one of +these functions (except the ``new[]`` operator) is called by a constant function +pointer. Cases where ``1`` is added both to the parameter and the result of the +``strlen()``-like function are ignored, as are cases where the whole addition is +surrounded by extra parentheses. + +`C` example code: + +.. code-block:: c + + void bad_malloc(char *str) { + char *c = (char*) malloc(strlen(str + 1)); + } + + +The suggested fix is to add ``1`` to the return value of ``strlen()`` and not +to its argument. In the example above the fix would be + +.. code-block:: c + + char *c = (char*) malloc(strlen(str) + 1); + + +`C++` example code: + +.. code-block:: c++ + + void bad_new(char *str) { + char *c = new char[strlen(str + 1)]; + } + + +As in the `C` code with the ``malloc()`` function, the suggested fix is to +add ``1`` to the return value of ``strlen()`` and not to its argument. In the +example above the fix would be + +.. code-block:: c++ + + char *c = new char[strlen(str) + 1]; + + +Example for silencing the diagnostic: + +.. code-block:: c + + void bad_malloc(char *str) { + char *c = (char*) malloc(strlen((str + 1))); + } diff --git a/docs/clang-tidy/checks/bugprone-misplaced-widening-cast.rst b/docs/clang-tidy/checks/bugprone-misplaced-widening-cast.rst new file mode 100644 index 000000000..f42be76ef --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-misplaced-widening-cast.rst @@ -0,0 +1,65 @@ +.. title:: clang-tidy - bugprone-misplaced-widening-cast + +bugprone-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/bugprone-move-forwarding-reference.rst b/docs/clang-tidy/checks/bugprone-move-forwarding-reference.rst new file mode 100644 index 000000000..3b57d5054 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-move-forwarding-reference.rst @@ -0,0 +1,60 @@ +.. title:: clang-tidy - bugprone-move-forwarding-reference + +bugprone-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/bugprone-multiple-statement-macro.rst b/docs/clang-tidy/checks/bugprone-multiple-statement-macro.rst new file mode 100644 index 000000000..708dc9506 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-multiple-statement-macro.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - bugprone-multiple-statement-macro + +bugprone-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/bugprone-parent-virtual-call.rst b/docs/clang-tidy/checks/bugprone-parent-virtual-call.rst new file mode 100755 index 000000000..e1021b188 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-parent-virtual-call.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - bugprone-parent-virtual-call + +bugprone-parent-virtual-call +============================ + +Detects and fixes calls to grand-...parent virtual methods instead of calls +to overridden parent's virtual methods. + +.. code-block:: c++ + + class A { + int virtual foo() {...} + }; + + class B: public A { + int foo() override {...} + }; + + class C: public B { + int foo() override { A::foo(); } + // ^^^^^^^^ + // warning: qualified name A::foo refers to a member overridden in subclass; did you mean 'B'? [bugprone-parent-virtual-call] + }; diff --git a/docs/clang-tidy/checks/bugprone-sizeof-container.rst b/docs/clang-tidy/checks/bugprone-sizeof-container.rst new file mode 100644 index 000000000..fb2f0b2a6 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-sizeof-container.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - bugprone-sizeof-container + +bugprone-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/bugprone-sizeof-expression.rst b/docs/clang-tidy/checks/bugprone-sizeof-expression.rst new file mode 100644 index 000000000..b58deaaef --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-sizeof-expression.rst @@ -0,0 +1,189 @@ +.. title:: clang-tidy - bugprone-sizeof-expression + +bugprone-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(expr)' +---------------------------------- + +In cases, where there is an enum or integer to represent a type, a common +mistake is to query the ``sizeof`` on the integer or enum that represents the +type that should be used by ``sizeof``. This results in the size of the integer +and not of the type the integer represents: + +.. code-block:: c++ + + enum data_type { + FLOAT_TYPE, + DOUBLE_TYPE + }; + + struct data { + data_type type; + void* buffer; + data_type get_type() { + return type; + } + }; + + void f(data d, int numElements) { + // should be sizeof(float) or sizeof(double), depending on d.get_type() + int numBytes = numElements * sizeof(d.get_type()); + ... + } + + +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:: WarnOnSizeOfIntegerExpression + + When non-zero, the check will warn on an expression like ``sizeof(expr)`` + where the expression results in an integer. Default is `0`. + +.. 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/bugprone-string-constructor.rst b/docs/clang-tidy/checks/bugprone-string-constructor.rst new file mode 100644 index 000000000..5deedf1f6 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-string-constructor.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - bugprone-string-constructor + +bugprone-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 str('x', 50); // should be str(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/bugprone-string-integer-assignment.rst b/docs/clang-tidy/checks/bugprone-string-integer-assignment.rst new file mode 100644 index 000000000..6401f008d --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-string-integer-assignment.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - bugprone-string-integer-assignment + +bugprone-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/bugprone-string-literal-with-embedded-nul.rst b/docs/clang-tidy/checks/bugprone-string-literal-with-embedded-nul.rst new file mode 100644 index 000000000..c1c4d3261 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-string-literal-with-embedded-nul.rst @@ -0,0 +1,36 @@ +.. title:: clang-tidy - bugprone-string-literal-with-embedded-nul + +bugprone-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/bugprone-suspicious-enum-usage.rst b/docs/clang-tidy/checks/bugprone-suspicious-enum-usage.rst new file mode 100644 index 000000000..5e765be3e --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-suspicious-enum-usage.rst @@ -0,0 +1,78 @@ +.. title:: clang-tidy - bugprone-suspicious-enum-usage + +bugprone-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/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-suspicious-missing-comma.rst b/docs/clang-tidy/checks/bugprone-suspicious-missing-comma.rst new file mode 100644 index 000000000..9fe915311 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-suspicious-missing-comma.rst @@ -0,0 +1,59 @@ +.. title:: clang-tidy - bugprone-suspicious-missing-comma + +bugprone-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/bugprone-suspicious-semicolon.rst b/docs/clang-tidy/checks/bugprone-suspicious-semicolon.rst new file mode 100644 index 000000000..a3eb098aa --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-suspicious-semicolon.rst @@ -0,0 +1,72 @@ +.. title:: clang-tidy - bugprone-suspicious-semicolon + +bugprone-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/bugprone-suspicious-string-compare.rst b/docs/clang-tidy/checks/bugprone-suspicious-string-compare.rst new file mode 100644 index 000000000..be3d2b6fb --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-suspicious-string-compare.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - bugprone-suspicious-string-compare + +bugprone-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/bugprone-swapped-arguments.rst b/docs/clang-tidy/checks/bugprone-swapped-arguments.rst new file mode 100644 index 000000000..30ae925da --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-swapped-arguments.rst @@ -0,0 +1,6 @@ +.. title:: clang-tidy - bugprone-swapped-arguments + +bugprone-swapped-arguments +========================== + +Finds potentially swapped arguments by looking at implicit conversions. diff --git a/docs/clang-tidy/checks/bugprone-terminating-continue.rst b/docs/clang-tidy/checks/bugprone-terminating-continue.rst new file mode 100644 index 000000000..1a6ae812f --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-terminating-continue.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - bugprone-terminating-continue + +bugprone-terminating-continue +============================= + +Detects `do while` loops with a condition always evaluating to false that +have a `continue` statement, as this `continue` terminates the loop +effectively. + +.. code-block:: c++ + + void f() { + do { + // some code + continue; // terminating continue + // some other code + } while(false); diff --git a/docs/clang-tidy/checks/bugprone-throw-keyword-missing.rst b/docs/clang-tidy/checks/bugprone-throw-keyword-missing.rst new file mode 100644 index 000000000..240a62ed6 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-throw-keyword-missing.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - bugprone-throw-keyword-missing + +bugprone-throw-keyword-missing +============================== + +Warns about a potentially missing ``throw`` keyword. If a temporary object is created, but the +object's type derives from (or is the same as) a class that has 'EXCEPTION', 'Exception' or +'exception' in its name, we can assume that the programmer's intention was to throw that object. + +Example: + +.. code-block:: c++ + + void f(int i) { + if (i < 0) { + // Exception is created but is not thrown. + std::runtime_error("Unexpected argument"); + } + } + + 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/bugprone-undelegated-constructor.rst b/docs/clang-tidy/checks/bugprone-undelegated-constructor.rst new file mode 100644 index 000000000..b32ed386f --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-undelegated-constructor.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - bugprone-undelegated-constructor + +bugprone-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/bugprone-unused-raii.rst b/docs/clang-tidy/checks/bugprone-unused-raii.rst new file mode 100644 index 000000000..f1987c531 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-unused-raii.rst @@ -0,0 +1,30 @@ +.. title:: clang-tidy - bugprone-unused-raii + +bugprone-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/bugprone-unused-return-value.rst b/docs/clang-tidy/checks/bugprone-unused-return-value.rst new file mode 100644 index 000000000..68d8e739f --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-unused-return-value.rst @@ -0,0 +1,31 @@ +.. title:: clang-tidy - bugprone-unused-return-value + +bugprone-unused-return-value +============================ + +Warns on unused function return values. The checked funtions can be configured. + +Options +------- + +.. option:: CheckedFunctions + + Semicolon-separated list of functions to check. Defaults to + ``::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty``. + This means that the calls to following functions are checked by default: + + - ``std::async()``. Not using the return value makes the call synchronous. + - ``std::launder()``. Not using the return value usually means that the + function interface was misunderstood by the programmer. Only the returned + pointer is "laundered", not the argument. + - ``std::remove()``, ``std::remove_if()`` and ``std::unique()``. The returned + iterator indicates the boundary between elements to keep and elements to be + removed. Not using the return value means that the information about which + elements to remove is lost. + - ``std::unique_ptr::release()``. Not using the return value can lead to + resource leaks if the same pointer isn't stored anywhere else. Often, + ignoring the ``release()`` return value indicates that the programmer + confused the function with ``reset()``. + - ``std::basic_string::empty()`` and ``std::vector::empty()``. Not using the + return value often indicates that the programmer confused the function with + ``clear()``. diff --git a/docs/clang-tidy/checks/bugprone-use-after-move.rst b/docs/clang-tidy/checks/bugprone-use-after-move.rst new file mode 100644 index 000000000..05b0e094d --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-use-after-move.rst @@ -0,0 +1,203 @@ +.. title:: clang-tidy - bugprone-use-after-move + +bugprone-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/bugprone-virtual-near-miss.rst b/docs/clang-tidy/checks/bugprone-virtual-near-miss.rst new file mode 100644 index 000000000..ac2747971 --- /dev/null +++ b/docs/clang-tidy/checks/bugprone-virtual-near-miss.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - bugprone-virtual-near-miss + +bugprone-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/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-msc32-c.rst b/docs/clang-tidy/checks/cert-msc32-c.rst new file mode 100644 index 000000000..df527ec13 --- /dev/null +++ b/docs/clang-tidy/checks/cert-msc32-c.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-msc32-c +.. meta:: + :http-equiv=refresh: 5;URL=cert-msc51-cpp.html + +cert-msc32-c +============ + +The cert-msc32-c check is an alias, please see +`cert-msc51-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-msc51-cpp.rst b/docs/clang-tidy/checks/cert-msc51-cpp.rst new file mode 100644 index 000000000..536536989 --- /dev/null +++ b/docs/clang-tidy/checks/cert-msc51-cpp.rst @@ -0,0 +1,40 @@ +.. title:: clang-tidy - cert-msc51-cpp + +cert-msc51-cpp +============== + +This check flags all pseudo-random number engines, engine adaptor +instantiations and ``srand()`` when initialized or seeded with default argument, +constant expression or any user-configurable type. Pseudo-random number +engines seeded with a predictable value may cause vulnerabilities e.g. in +security protocols. +This is a CERT security rule, see +`MSC51-CPP. Ensure your random number generator is properly seeded +`_ and +`MSC32-C. Properly seed pseudorandom number generators +`_. + +Examples: + +.. code-block:: c++ + + void foo() { + std::mt19937 engine1; // Diagnose, always generate the same sequence + std::mt19937 engine2(1); // Diagnose + engine1.seed(); // Diagnose + engine2.seed(1); // Diagnose + + std::time_t t; + engine1.seed(std::time(&t)); // Diagnose, system time might be controlled by user + + int x = atoi(argv[1]); + std::mt19937 engine3(x); // Will not warn + } + +Options +------- + +.. option:: DisallowedSeedTypes + + A comma-separated list of the type names which are disallowed. + Default values are ``time_t``, ``std::time_t``. 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..dcd0fee8b --- /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=performance-move-constructor-init.html + +cert-oop11-cpp +============== + +The cert-oop11-cpp check is an alias, please see +`performance-move-constructor-init `_ +for more information. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-avoid-goto.rst b/docs/clang-tidy/checks/cppcoreguidelines-avoid-goto.rst new file mode 100644 index 000000000..fcb7dd313 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-avoid-goto.rst @@ -0,0 +1,50 @@ +.. title:: clang-tidy - cppcoreguidelines-avoid-goto + +cppcoreguidelines-avoid-goto +============================ + +The usage of ``goto`` for control flow is error prone and should be replaced +with looping constructs. Only forward jumps in nested loops are accepted. + +This check implements `ES.76 `_ +from the CppCoreGuidelines and +`6.3.1 from High Integrity C++ `_. + +For more information on why to avoid programming +with ``goto`` you can read the famous paper `A Case against the GO TO Statement. `_. + +The check diagnoses ``goto`` for backward jumps in every language mode. These +should be replaced with `C/C++` looping constructs. + +.. code-block:: c++ + + // Bad, handwritten for loop. + int i = 0; + // Jump label for the loop + loop_start: + do_some_operation(); + + if (i < 100) { + ++i; + goto loop_start; + } + + // Better + for(int i = 0; i < 100; ++i) + do_some_operation(); + +Modern C++ needs ``goto`` only to jump out of nested loops. + +.. code-block:: c++ + + for(int i = 0; i < 100; ++i) { + for(int j = 0; j < 100; ++j) { + if (i * j > 500) + goto early_exit; + } + } + + early_exit: + some_operation(); + +All other uses of ``goto`` are diagnosed in `C++`. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-c-copy-assignment-signature.rst b/docs/clang-tidy/checks/cppcoreguidelines-c-copy-assignment-signature.rst new file mode 100644 index 000000000..9096ef4d3 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-c-copy-assignment-signature.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cppcoreguidelines-c-copy-assignment-signature +.. meta:: + :http-equiv=refresh: 5;URL=misc-unconventional-assign-operator.html + +cppcoreguidelines-c-copy-assignment-signature +============================================= + +The cppcoreguidelines-c-copy-assignment-signature check is an alias, please see +`misc-unconventional-assign-operator `_ +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-narrowing-conversions.rst b/docs/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.rst new file mode 100644 index 000000000..b1e82cae8 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - cppcoreguidelines-narrowing-conversions + +cppcoreguidelines-narrowing-conversions +======================================= + +Checks for silent narrowing conversions, e.g: ``int i = 0; i += 0.1;``. While +the issue is obvious in this former example, it might not be so in the +following: ``void MyClass::f(double d) { int_member_ += d; }``. + +This rule is part of the "Expressions and statements" profile of the C++ Core +Guidelines, corresponding to rule ES.46. See + +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-narrowing. + +We enforce only part of the guideline, more specifically, we flag: + - All floating-point to integer conversions that are not marked by an explicit + cast (c-style or ``static_cast``). For example: ``int i = 0; i += 0.1;``, + ``void f(int); f(0.1);``, + - All applications of binary operators where the left-hand-side is an integer + and the right-hand-size is a floating-point. For example: + ``int i; i+= 0.1;``. + 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-owning-memory.rst b/docs/clang-tidy/checks/cppcoreguidelines-owning-memory.rst new file mode 100644 index 000000000..3f6f8c4b7 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-owning-memory.rst @@ -0,0 +1,176 @@ +.. title:: clang-tidy - cppcoreguidelines-owning-memory + +cppcoreguidelines-owning-memory +=============================== + +This check implements the type-based semantics of ``gsl::owner``, which allows +static analysis on code, that uses raw pointers to handle resources like +dynamic memory, but won't introduce RAII concepts. + +The relevant sections in the `C++ Core Guidelines `_ are I.11, C.33, R.3 and GSL.Views +The definition of a ``gsl::owner`` is straight forward + +.. code-block:: c++ + + namespace gsl { template owner = T; } + +It is therefore simple to introduce the owner even without using an implementation of +the `Guideline Support Library `_. + +All checks are purely type based and not (yet) flow sensitive. + +The following examples will demonstrate the correct and incorrect initializations +of owners, assignment is handled the same way. Note that both ``new`` and +``malloc()``-like resource functions are considered to produce resources. + +.. code-block:: c++ + + // Creating an owner with factory functions is checked. + gsl::owner function_that_returns_owner() { return gsl::owner(new int(42)); } + + // Dynamic memory must be assigned to an owner + int* Something = new int(42); // BAD, will be caught + gsl::owner Owner = new int(42); // Good + gsl::owner Owner = new int[42]; // Good as well + + // Returned owner must be assigned to an owner + int* Something = function_that_returns_owner(); // Bad, factory function + gsl::owner Owner = function_that_returns_owner(); // Good, result lands in owner + + // Something not a resource or owner should not be assigned to owners + int Stack = 42; + gsl::owner Owned = &Stack; // Bad, not a resource assigned + +In the case of dynamic memory as resource, only ``gsl::owner`` variables are allowed +to be deleted. + +.. code-block:: c++ + + // Example Bad, non-owner as resource handle, will be caught. + int* NonOwner = new int(42); // First warning here, since new must land in an owner + delete NonOwner; // Second warning here, since only owners are allowed to be deleted + + // Example Good, Ownership correclty stated + gsl::owner Owner = new int(42); // Good + delete Owner; // Good as well, statically enforced, that only owners get deleted + +The check will furthermore ensure, that functions, that expect a ``gsl::owner`` as +argument get called with either a ``gsl::owner`` or a newly created resource. + +.. code-block:: c++ + + void expects_owner(gsl::owner o) { delete o; } + + // Bad Code + int NonOwner = 42; + expects_owner(&NonOwner); // Bad, will get caught + + // Good Code + gsl::owner Owner = new int(42); + expects_owner(Owner); // Good + expects_owner(new int(42)); // Good as well, recognized created resource + + // Port legacy code for better resource-safety + gsl::owner File = fopen("my_file.txt", "rw+"); + FILE* BadFile = fopen("another_file.txt", "w"); // Bad, warned + + // ... use the file + + fclose(File); // Ok, File is annotated as 'owner<>' + fclose(BadFile); // BadFile is not an 'owner<>', will be warned + + +Options +------- + +.. option:: LegacyResourceProducers + + Semicolon-separated list of fully qualified names of legacy functions that create + resources but cannot introduce ``gsl::owner<>``. + Defaults to ``::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile``. + + +.. option:: LegacyResourceConsumers + + Semicolon-separated list of fully qualified names of legacy functions expecting + resource owners as pointer arguments but cannot introduce ``gsl::owner<>``. + Defaults to ``::free;::realloc;::freopen;::fclose``. + + +Limitations +----------- + +Using ``gsl::owner`` in a typedef or alias is not handled correctly. + +.. code-block:: c++ + + using heap_int = gsl::owner; + heap_int allocated = new int(42); // False positive! + +The ``gsl::owner`` is declared as a templated type alias. +In template functions and classes, like in the example below, the information +of the type aliases gets lost. Therefore using ``gsl::owner`` in a heavy templated +code base might lead to false positives. + +Known code constructs that do not get diagnosed correctly are: + +- ``std::exchange`` +- ``std::vector>`` + +.. code-block:: c++ + + // This template function works as expected. Type information doesn't get lost. + template + void delete_owner(gsl::owner owned_object) { + delete owned_object; // Everything alright + } + + gsl::owner function_that_returns_owner() { return gsl::owner(new int(42)); } + + // Type deduction does not work for auto variables. + // This is caught by the check and will be noted accordingly. + auto OwnedObject = function_that_returns_owner(); // Type of OwnedObject will be int* + + // Problematic function template that looses the typeinformation on owner + template + void bad_template_function(T some_object) { + // This line will trigger the warning, that a non-owner is assigned to an owner + gsl::owner new_owner = some_object; + } + + // Calling the function with an owner still yields a false positive. + bad_template_function(gsl::owner(new int(42))); + + + // The same issue occurs with templated classes like the following. + template + class OwnedValue { + public: + const T getValue() const { return _val; } + private: + T _val; + }; + + // Code, that yields a false positive. + OwnedValue> Owner(new int(42)); // Type deduction yield T -> int * + // False positive, getValue returns int* and not gsl::owner + gsl::owner OwnedInt = Owner.getValue(); + +Another limitation of the current implementation is only the type based checking. +Suppose you have code like the following: + +.. code-block:: c++ + + // Two owners with assigned resources + gsl::owner Owner1 = new int(42); + gsl::owner Owner2 = new int(42); + + Owner2 = Owner1; // Conceptual Leak of initial resource of Owner2! + Owner1 = nullptr; + +The semantic of a ``gsl::owner`` is mostly like a ``std::unique_ptr``, therefore +assignment of two ``gsl::owner`` is considered a move, which requires that the +resource ``Owner2`` must have been released before the assignment. +This kind of condition could be catched in later improvements of this check with +flowsensitive analysis. Currently, the `Clang Static Analyzer` catches this bug +for dynamic memory, but not for general types of resources. 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/fuchsia-default-arguments.rst b/docs/clang-tidy/checks/fuchsia-default-arguments.rst new file mode 100644 index 000000000..ab011bf34 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-default-arguments.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - fuchsia-default-arguments + +fuchsia-default-arguments +========================= + +Warns if a function or method is declared or called with default arguments. + +For example, the declaration: + +.. code-block:: c++ + + int foo(int value = 5) { return value; } + +will cause a warning. + +A function call expression that uses a default argument will be diagnosed. +Calling it without defaults will not cause a warning: + +.. code-block:: c++ + + foo(); // warning + foo(0); // no warning + +See the features disallowed in Fuchsia at https://fuchsia.googlesource.com/zircon/+/master/docs/cxx.md diff --git a/docs/clang-tidy/checks/fuchsia-header-anon-namespaces.rst b/docs/clang-tidy/checks/fuchsia-header-anon-namespaces.rst new file mode 100644 index 000000000..42b575323 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-header-anon-namespaces.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - fuchsia-header-anon-namespaces +.. meta:: + :http-equiv=refresh: 5;URL=google-build-namespaces.html + +fuchsia-header-anon-namespaces +============================== + +The fuchsia-header-anon-namespaces check is an alias, please see +`google-build-namespace `_ +for more information. diff --git a/docs/clang-tidy/checks/fuchsia-multiple-inheritance.rst b/docs/clang-tidy/checks/fuchsia-multiple-inheritance.rst new file mode 100644 index 000000000..b52ec2be7 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-multiple-inheritance.rst @@ -0,0 +1,46 @@ +.. title:: clang-tidy - fuchsia-multiple-inheritance + +fuchsia-multiple-inheritance +============================ + +Warns if a class inherits from multiple classes that are not pure virtual. + +For example, declaring a class that inherits from multiple concrete classes is +disallowed: + +.. code-block:: c++ + + class Base_A { + public: + virtual int foo() { return 0; } + }; + + class Base_B { + public: + virtual int bar() { return 0; } + }; + + // Warning + class Bad_Child1 : public Base_A, Base_B {}; + +A class that inherits from a pure virtual is allowed: + +.. code-block:: c++ + + class Interface_A { + public: + virtual int foo() = 0; + }; + + class Interface_B { + public: + virtual int bar() = 0; + }; + + // No warning + class Good_Child1 : public Interface_A, Interface_B { + virtual int foo() override { return 0; } + virtual int bar() override { return 0; } + }; + +See the features disallowed in Fuchsia at https://fuchsia.googlesource.com/zircon/+/master/docs/cxx.md diff --git a/docs/clang-tidy/checks/fuchsia-overloaded-operator.rst b/docs/clang-tidy/checks/fuchsia-overloaded-operator.rst new file mode 100644 index 000000000..070780f48 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-overloaded-operator.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - fuchsia-overloaded-operator + +fuchsia-overloaded-operator +=========================== + +Warns if an operator is overloaded, except for the assignment (copy and move) +operators. + +For example: + +.. code-block:: c++ + + int operator+(int); // Warning + + B &operator=(const B &Other); // No warning + B &operator=(B &&Other) // No warning + +See the features disallowed in Fuchsia at https://fuchsia.googlesource.com/zircon/+/master/docs/cxx.md diff --git a/docs/clang-tidy/checks/fuchsia-restrict-system-includes.rst b/docs/clang-tidy/checks/fuchsia-restrict-system-includes.rst new file mode 100644 index 000000000..622e025d1 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-restrict-system-includes.rst @@ -0,0 +1,32 @@ +.. title:: clang-tidy - fuchsia-restrict-system-includes + +fuchsia-restrict-system-includes +================================ + +Checks for allowed system includes and suggests removal of any others. + +It is important to note that running this check with fixes may break code, as +the fix removes headers. Fixes are applied to source and header files, but not +to system headers. + +For example, given the allowed system includes 'a.h,b*': + +.. code-block:: c++ + + #include + #include + #include + #include // Warning, as c.h is not explicitly allowed + +All system includes can be allowed with '*', and all can be disallowed with an +empty string (''). + +Options +------- + +.. option:: Includes + + A string containing a comma separated glob list of allowed include filenames. + Similar to the -checks glob list for running clang-tidy itself, the two + wildcard characters are '*' and '-', to include and exclude globs, + respectively.The default is '*', which allows all includes. diff --git a/docs/clang-tidy/checks/fuchsia-statically-constructed-objects.rst b/docs/clang-tidy/checks/fuchsia-statically-constructed-objects.rst new file mode 100644 index 000000000..7c1c0aeb7 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-statically-constructed-objects.rst @@ -0,0 +1,43 @@ +.. title:: clang-tidy - fuchsia-statically-constructed-objects + +fuchsia-statically-constructed-objects +====================================== + +Warns if global, non-trivial objects with static storage are constructed, unless +the object is statically initialized with a ``constexpr`` constructor or has no +explicit constructor. + +For example: + +.. code-block:: c++ + + class A {}; + + class B { + public: + B(int Val) : Val(Val) {} + private: + int Val; + }; + + class C { + public: + C(int Val) : Val(Val) {} + constexpr C() : Val(0) {} + + private: + int Val; + }; + + static A a; // No warning, as there is no explicit constructor + static C c(0); // No warning, as constructor is constexpr + + static B b(0); // Warning, as constructor is not constexpr + static C c2(0, 1); // Warning, as constructor is not constexpr + + static int i; // No warning, as it is trivial + + extern int get_i(); + static C(get_i()) // Warning, as the constructor is dynamically initialized + +See the features disallowed in Fuchsia at https://fuchsia.googlesource.com/zircon/+/master/docs/cxx.md diff --git a/docs/clang-tidy/checks/fuchsia-trailing-return.rst b/docs/clang-tidy/checks/fuchsia-trailing-return.rst new file mode 100644 index 000000000..e67f47728 --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-trailing-return.rst @@ -0,0 +1,35 @@ +.. title:: clang-tidy - fuchsia-trailing-return + +fuchsia-trailing-return +======================= + +Functions that have trailing returns are disallowed, except for those using +``decltype`` specifiers and lambda with otherwise unutterable return types. + +For example: + +.. code-block:: c++ + + // No warning + int add_one(const int arg) { return arg; } + + // Warning + auto get_add_one() -> int (*)(const int) { + return add_one; + } + +Exceptions are made for lambdas and ``decltype`` specifiers: + +.. code-block:: c++ + + // No warning + auto lambda = [](double x, double y) -> double {return x + y;}; + + // No warning + template + auto fn(const T1 &lhs, const T2 &rhs) -> decltype(lhs + rhs) { + return lhs + rhs; + } + + +See the features disallowed in Fuchsia at https://fuchsia.googlesource.com/zircon/+/master/docs/cxx.md diff --git a/docs/clang-tidy/checks/fuchsia-virtual-inheritance.rst b/docs/clang-tidy/checks/fuchsia-virtual-inheritance.rst new file mode 100644 index 000000000..f73a49b2b --- /dev/null +++ b/docs/clang-tidy/checks/fuchsia-virtual-inheritance.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - fuchsia-virtual-inheritance + +fuchsia-virtual-inheritance +=========================== + +Warns if classes are defined with virtual inheritance. + +For example, classes should not be defined with virtual inheritance: + +.. code-block:: c++ + + class B : public virtual A {}; // warning + +See the features disallowed in Fuchsia at https://fuchsia.googlesource.com/zircon/+/master/docs/cxx.md 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..69c01d372 --- /dev/null +++ b/docs/clang-tidy/checks/google-build-namespaces.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - google-build-namespaces + +google-build-namespaces +======================= + +`cert-dcl59-cpp` redirects here as an alias for this check. +`fuchsia-header-anon-namespaces` 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-objc-avoid-throwing-exception.rst b/docs/clang-tidy/checks/google-objc-avoid-throwing-exception.rst new file mode 100644 index 000000000..39b021745 --- /dev/null +++ b/docs/clang-tidy/checks/google-objc-avoid-throwing-exception.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - google-objc-avoid-throwing-exception + +google-objc-avoid-throwing-exception +==================================== + +Finds uses of throwing exceptions usages in Objective-C files. + +For the same reason as the Google C++ style guide, we prefer not throwing +exceptions from Objective-C code. + +The corresponding C++ style guide rule: +https://google.github.io/styleguide/cppguide.html#Exceptions + +Instead, prefer passing in ``NSError **`` and return ``BOOL`` to indicate success or failure. + +A counterexample: + +.. code-block:: objc + + - (void)readFile { + if ([self isError]) { + @throw [NSException exceptionWithName:...]; + } + } + +Instead, returning an error via ``NSError **`` is preferred: + +.. code-block:: objc + + - (BOOL)readFileWithError:(NSError **)error { + if ([self isError]) { + *error = [NSError errorWithDomain:...]; + return NO; + } + return YES; + } + +The corresponding style guide rule: +http://google.github.io/styleguide/objcguide.html#avoid-throwing-exceptions diff --git a/docs/clang-tidy/checks/google-objc-global-variable-declaration.rst b/docs/clang-tidy/checks/google-objc-global-variable-declaration.rst new file mode 100644 index 000000000..d4703706b --- /dev/null +++ b/docs/clang-tidy/checks/google-objc-global-variable-declaration.rst @@ -0,0 +1,47 @@ +.. title:: clang-tidy - google-objc-global-variable-declaration + +google-objc-global-variable-declaration +======================================= + +Finds global variable declarations in Objective-C files that do not follow the +pattern of variable names in Google's Objective-C Style Guide. + +The corresponding style guide rule: +http://google.github.io/styleguide/objcguide.html#variable-names + +All the global variables should follow the pattern of `g[A-Z].*` (variables) or +`k[A-Z].*` (constants). The check will suggest a variable name that follows the +pattern if it can be inferred from the original name. + +For code: + +.. code-block:: objc + + static NSString* myString = @"hello"; + +The fix will be: + +.. code-block:: objc + + static NSString* gMyString = @"hello"; + +Another example of constant: + +.. code-block:: objc + + static NSString* const myConstString = @"hello"; + +The fix will be: + +.. code-block:: objc + + static NSString* const kMyConstString = @"hello"; + +However for code that prefixed with non-alphabetical characters like: + +.. code-block:: objc + + static NSString* __anotherString = @"world"; + +The check will give a warning message but will not be able to suggest a fix. The +user need to fix it on his own. 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-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-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-avoid-goto.rst b/docs/clang-tidy/checks/hicpp-avoid-goto.rst new file mode 100644 index 000000000..f689bec6f --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-avoid-goto.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - hicpp-avoid-goto + +hicpp-avoid-goto +================ + +The `hicpp-avoid-goto` check is an alias to +`cppcoreguidelines-avoid-goto `_. +Rule `6.3.1 High Integrity C++ `_ +requires that ``goto`` only skips parts of a block and is not used for other +reasons. + +Both coding guidelines implement the same exception to the usage of ``goto``. diff --git a/docs/clang-tidy/checks/hicpp-braces-around-statements.rst b/docs/clang-tidy/checks/hicpp-braces-around-statements.rst new file mode 100644 index 000000000..2931aa883 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-braces-around-statements.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-braces-around-statements +.. meta:: + :http-equiv=refresh: 5;URL=readability-braces-around-statements.html + +hicpp-braces-around-statements +============================== + +The `hicpp-braces-around-statements` check is an alias, please see +`readability-braces-around-statements `_ +for more information. +It enforces the `rule 6.1.1 `_. diff --git a/docs/clang-tidy/checks/hicpp-deprecated-headers.rst b/docs/clang-tidy/checks/hicpp-deprecated-headers.rst new file mode 100644 index 000000000..960d918c7 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-deprecated-headers.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-deprecated-headers +.. meta:: + :http-equiv=refresh: 5;URL=modernize-deprecated-headers.html + +hicpp-deprecated-headers +======================== + +The `hicpp-deprecated-headers` check is an alias, please see +`modernize-deprecated-headers `_ +for more information. +It enforces the `rule 1.3.3 `_. diff --git a/docs/clang-tidy/checks/hicpp-exception-baseclass.rst b/docs/clang-tidy/checks/hicpp-exception-baseclass.rst new file mode 100644 index 000000000..42563aeae --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-exception-baseclass.rst @@ -0,0 +1,30 @@ +.. title:: clang-tidy - hicpp-exception-baseclass + +hicpp-exception-baseclass +========================= + +Ensure that every value that in a ``throw`` expression is an instance of +``std::exception``. + +This enforces `rule 15.1 `_ +of the High Integrity C++ Coding Standard. + +.. code-block:: c++ + + class custom_exception {}; + + void throwing() noexcept(false) { + // Problematic throw expressions. + throw int(42); + throw custom_exception(); + } + + class mathematical_error : public std::exception {}; + + void throwing2() noexcept(false) { + // These kind of throws are ok. + throw mathematical_error(); + throw std::runtime_error(); + throw std::exception(); + } + 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..3ee0b789e --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-explicit-conversions.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - hicpp-explicit-conversions +.. meta:: + :http-equiv=refresh: 5;URL=google-explicit-constructor.html + +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..4b1f616a9 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-function-size.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - hicpp-function-size +.. meta:: + :http-equiv=refresh: 5;URL=readability-function-size.html + +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..4fac3f552 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-invalid-access-moved.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-invalid-access-moved +.. meta:: + :http-equiv=refresh: 5;URL=bugprone-use-after-move.html + +hicpp-invalid-access-moved +========================== + +This check is an alias for `bugprone-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..7f985e21e --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-member-init.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-member-init +.. meta:: + :http-equiv=refresh: 5;URL=cppcoreguidelines-pro-type-member-init.html + +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-move-const-arg.rst b/docs/clang-tidy/checks/hicpp-move-const-arg.rst new file mode 100644 index 000000000..c8d34d0be --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-move-const-arg.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-move-const-arg +.. meta:: + :http-equiv=refresh: 5;URL=performance-move-const-arg.html + +hicpp-move-const-arg +==================== + +The `hicpp-move-const-arg` check is an alias, please see +`performance-move-const-arg `_ for more information. +It enforces the `rule 17.3.1 `_. diff --git a/docs/clang-tidy/checks/hicpp-multiway-paths-covered.rst b/docs/clang-tidy/checks/hicpp-multiway-paths-covered.rst new file mode 100644 index 000000000..2cace6370 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-multiway-paths-covered.rst @@ -0,0 +1,96 @@ +.. title:: clang-tidy - hicpp-multiway-paths-covered + +hicpp-multiway-paths-covered +============================ + +This check discovers situations where code paths are not fully-covered. +It furthermore suggests using ``if`` instead of ``switch`` if the code will be more clear. +The `rule 6.1.2 `_ +and `rule 6.1.4 `_ +of the High Integrity C++ Coding Standard are enforced. + +``if-else if`` chains that miss a final ``else`` branch might lead to unexpected +program execution and be the result of a logical error. +If the missing ``else`` branch is intended you can leave it empty with a clarifying +comment. +This warning can be noisy on some code bases, so it is disabled by default. + +.. code-block:: c++ + + void f1() { + int i = determineTheNumber(); + + if(i > 0) { + // Some Calculation + } else if (i < 0) { + // Precondition violated or something else. + } + // ... + } + +Similar arguments hold for ``switch`` statements which do not cover all possible code paths. + +.. code-block:: c++ + + // The missing default branch might be a logical error. It can be kept empty + // if there is nothing to do, making it explicit. + void f2(int i) { + switch (i) { + case 0: // something + break; + case 1: // something else + break; + } + // All other numbers? + } + + // Violates this rule as well, but already emits a compiler warning (-Wswitch). + enum Color { Red, Green, Blue, Yellow }; + void f3(enum Color c) { + switch (c) { + case Red: // We can't drive for now. + break; + case Green: // We are allowed to drive. + break; + } + // Other cases missing + } + + +The `rule 6.1.4 `_ +requires every ``switch`` statement to have at least two ``case`` labels other than a `default` label. +Otherwise, the ``switch`` could be better expressed with an ``if`` statement. +Degenerated ``switch`` statements without any labels are caught as well. + +.. code-block:: c++ + + // Degenerated switch that could be better written as `if` + int i = 42; + switch(i) { + case 1: // do something here + default: // do somethe else here + } + + // Should rather be the following: + if (i == 1) { + // do something here + } + else { + // do something here + } + + +.. code-block:: c++ + + // A completly degenerated switch will be diagnosed. + int i = 42; + switch(i) {} + + +Options +------- + +.. option:: WarnOnMissingElse + + Boolean flag that activates a warning for missing ``else`` branches. + Default is `0`. 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..4bb850510 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-named-parameter.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-named-parameter +.. meta:: + :http-equiv=refresh: 5;URL=readability-named-parameter.html + +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..88c26ad2b --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-new-delete-operators.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-new-delete-operators +.. meta:: + :http-equiv=refresh: 5;URL=misc-new-delete-overloads.html + +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-array-decay.rst b/docs/clang-tidy/checks/hicpp-no-array-decay.rst new file mode 100644 index 000000000..01f140e4a --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-no-array-decay.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-no-array-decay +.. meta:: + :http-equiv=refresh: 5;URL=cppcoreguidelines-pro-bounds-array-to-pointer-decay.html + +hicpp-no-array-decay +==================== + +The `hicpp-no-array-decay` check is an alias, please see +`cppcoreguidelines-pro-bounds-array-to-pointer-decay `_ +for more information. +It enforces the `rule 4.1.1 `_. 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-no-malloc.rst b/docs/clang-tidy/checks/hicpp-no-malloc.rst new file mode 100644 index 000000000..768342fc2 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-no-malloc.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-no-malloc +.. meta:: + :http-equiv=refresh: 5;URL=cppcoreguidelines-no-malloc.html + +hicpp-no-malloc +=============== + +The `hicpp-no-malloc` check is an alias, please see +`cppcoreguidelines-no-malloc `_ +for more information. +It enforces the `rule 5.3.2 `_. 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..10573eca7 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-noexcept-move.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - hicpp-noexcept-move +.. meta:: + :http-equiv=refresh: 5;URL=misc-noexcept-moveconstructor.html + +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-signed-bitwise.rst b/docs/clang-tidy/checks/hicpp-signed-bitwise.rst new file mode 100644 index 000000000..59b19b6f3 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-signed-bitwise.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - hicpp-signed-bitwise + +hicpp-signed-bitwise +==================== + +Finds uses of bitwise operations on signed integer types, which may lead to +undefined or implementation defined behaviour. + +The according rule is defined in the `High Integrity C++ Standard, Section 5.6.1 `_. 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..417317731 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-special-member-functions.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - hicpp-special-member-functions +.. meta:: + :http-equiv=refresh: 5;URL=cppcoreguidelines-special-member-functions.html + +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-static-assert.rst b/docs/clang-tidy/checks/hicpp-static-assert.rst new file mode 100644 index 000000000..b5d4e41bb --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-static-assert.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-static-assert +.. meta:: + :http-equiv=refresh: 5;URL=misc-static-assert.html + +hicpp-static-assert +=================== + +The `hicpp-static-assert` check is an alias, please see +`misc-static-assert `_ for more information. +It enforces the `rule 7.1.10 `_. 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..db521c583 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-undelegated-constructor.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - hicpp-undelegated-construtor +.. meta:: + :http-equiv=refresh: 5;URL=bugprone-undelegated-constructor.html + +hicpp-undelegated-constructor +============================= + +This check is an alias for `bugprone-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-auto.rst b/docs/clang-tidy/checks/hicpp-use-auto.rst new file mode 100644 index 000000000..23d006740 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-auto.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-use-auto +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-auto.html + +hicpp-use-auto +============== + +The `hicpp-use-auto` check is an alias, please see +`modernize-use-auto `_ for more information. +It enforces the `rule 7.1.8 `_. diff --git a/docs/clang-tidy/checks/hicpp-use-emplace.rst b/docs/clang-tidy/checks/hicpp-use-emplace.rst new file mode 100644 index 000000000..07853c8aa --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-emplace.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-use-emplace +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-emplace.html + +hicpp-use-emplace +================= + +The `hicpp-use-emplace` check is an alias, please see +`modernize-use-emplace `_ for more information. +It enforces the `rule 17.4.2 `_. 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..1dcf581ec --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-equals-default.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - hicpp-use-equals-defaults +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-equals-default.html + +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..4d987869a --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-equals-delete.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-use-equals-delete +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-equals-delete.html + +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-noexcept.rst b/docs/clang-tidy/checks/hicpp-use-noexcept.rst new file mode 100644 index 000000000..1a30ddf55 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-noexcept.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-use-noexcept +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-noexcept.html + +hicpp-use-noexcept +================== + +The `hicpp-use-noexcept` check is an alias, please see +`modernize-use-noexcept `_ for more information. +It enforces the `rule 1.3.5 `_. diff --git a/docs/clang-tidy/checks/hicpp-use-nullptr.rst b/docs/clang-tidy/checks/hicpp-use-nullptr.rst new file mode 100644 index 000000000..9bd905143 --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-nullptr.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-use-nullptr +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-nullptr.html + +hicpp-use-nullptr +================= + +The `hicpp-use-nullptr` check is an alias, please see +`modernize-use-nullptr `_ for more information. +It enforces the `rule 2.5.3 `_. 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..62541fa7a --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-use-override.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - hicpp-use-override +.. meta:: + :http-equiv=refresh: 5;URL=modernize-use-override.html + +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/hicpp-vararg.rst b/docs/clang-tidy/checks/hicpp-vararg.rst new file mode 100644 index 000000000..92562e43d --- /dev/null +++ b/docs/clang-tidy/checks/hicpp-vararg.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - hicpp-vararg +.. meta:: + :http-equiv=refresh: 5;URL=cppcoreguidelines-pro-type-vararg.html + +hicpp-vararg +============ + +The `hicpp-vararg` check is an alias, please see +`cppcoreguidelines-pro-type-vararg `_ +for more information. +It enforces the `rule 14.1.1 `_. diff --git a/docs/clang-tidy/checks/list.rst b/docs/clang-tidy/checks/list.rst new file mode 100644 index 000000000..9bb68bcd6 --- /dev/null +++ b/docs/clang-tidy/checks/list.rst @@ -0,0 +1,238 @@ +.. title:: clang-tidy - Clang-Tidy Checks + +Clang-Tidy Checks +================= + +.. toctree:: + abseil-string-find-startswith + android-cloexec-accept + android-cloexec-accept4 + android-cloexec-creat + android-cloexec-dup + android-cloexec-epoll-create + android-cloexec-epoll-create1 + android-cloexec-fopen + android-cloexec-inotify-init + android-cloexec-inotify-init1 + android-cloexec-memfd-create + android-cloexec-open + android-cloexec-socket + android-comparison-in-temp-failure-retry + boost-use-to-string + bugprone-argument-comment + bugprone-assert-side-effect + bugprone-bool-pointer-implicit-conversion + bugprone-copy-constructor-init + bugprone-dangling-handle + bugprone-exception-escape + bugprone-fold-init-type + bugprone-forward-declaration-namespace + bugprone-forwarding-reference-overload + bugprone-inaccurate-erase + bugprone-incorrect-roundings + bugprone-integer-division + bugprone-lambda-function-name + bugprone-macro-parentheses + bugprone-macro-repeated-side-effects + bugprone-misplaced-operator-in-strlen-in-alloc + bugprone-misplaced-widening-cast + bugprone-move-forwarding-reference + bugprone-multiple-statement-macro + bugprone-parent-virtual-call + bugprone-sizeof-container + bugprone-sizeof-expression + bugprone-string-constructor + bugprone-string-integer-assignment + bugprone-string-literal-with-embedded-nul + bugprone-suspicious-enum-usage + bugprone-suspicious-memset-usage + bugprone-suspicious-missing-comma + bugprone-suspicious-semicolon + bugprone-suspicious-string-compare + bugprone-swapped-arguments + bugprone-terminating-continue + bugprone-throw-keyword-missing + bugprone-undefined-memory-manipulation + bugprone-undelegated-constructor + bugprone-unused-raii + bugprone-unused-return-value + bugprone-use-after-move + bugprone-virtual-near-miss + 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-msc32-c (redirects to cert-msc51-cpp) + cert-msc50-cpp + cert-msc51-cpp + cert-oop11-cpp (redirects to performance-move-constructor-init) + cppcoreguidelines-avoid-goto + cppcoreguidelines-c-copy-assignment-signature (redirects to misc-unconventional-assign-operator) + cppcoreguidelines-interfaces-global-init + cppcoreguidelines-narrowing-conversions + cppcoreguidelines-no-malloc + cppcoreguidelines-owning-memory + 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 + fuchsia-default-arguments + fuchsia-header-anon-namespaces (redirects to google-build-namespaces) + fuchsia-multiple-inheritance + fuchsia-overloaded-operator + fuchsia-restrict-system-includes + fuchsia-statically-constructed-objects + fuchsia-trailing-return + fuchsia-virtual-inheritance + google-build-explicit-make-pair + google-build-namespaces + google-build-using-namespace + google-default-arguments + google-explicit-constructor + google-global-names-in-headers + google-objc-avoid-throwing-exception + google-objc-global-variable-declaration + 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-todo + google-runtime-int + google-runtime-operator + google-runtime-references + hicpp-avoid-goto + hicpp-braces-around-statements (redirects to readability-braces-around-statements) + hicpp-deprecated-headers (redirects to modernize-deprecated-headers) + hicpp-exception-baseclass + hicpp-explicit-conversions (redirects to google-explicit-constructor) + hicpp-function-size (redirects to readability-function-size) + hicpp-invalid-access-moved (redirects to bugprone-use-after-move) + hicpp-member-init (redirects to cppcoreguidelines-pro-type-member-init) + hicpp-move-const-arg (redirects to performance-move-const-arg) + hicpp-multiway-paths-covered + hicpp-named-parameter (redirects to readability-named-parameter) + hicpp-new-delete-operators (redirects to misc-new-delete-overloads) + hicpp-no-array-decay (redirects to cppcoreguidelines-pro-bounds-array-to-pointer-decay) + hicpp-no-assembler + hicpp-no-malloc (redirects to cppcoreguidelines-no-malloc) + hicpp-noexcept-move (redirects to misc-noexcept-moveconstructor) + hicpp-signed-bitwise + hicpp-special-member-functions (redirects to cppcoreguidelines-special-member-functions) + hicpp-static-assert (redirects to misc-static-assert) + hicpp-undelegated-constructor (redirects to bugprone-undelegated-constructor) + hicpp-use-auto (redirects to modernize-use-auto) + hicpp-use-emplace (redirects to modernize-use-emplace) + hicpp-use-equals-default (redirects to modernize-use-equals-default) + hicpp-use-equals-delete (redirects to modernize-use-equals-delete) + hicpp-use-noexcept (redirects to modernize-use-noexcept) + hicpp-use-nullptr (redirects to modernize-use-nullptr) + hicpp-use-override (redirects to modernize-use-override) + hicpp-vararg (redirects to cppcoreguidelines-pro-type-vararg) + llvm-header-guard + llvm-include-order + llvm-namespace-comment + llvm-twine-local + misc-definitions-in-headers + misc-misplaced-const + misc-new-delete-overloads + misc-non-copyable-objects + misc-redundant-expression + misc-static-assert + misc-throw-by-value-catch-by-reference + misc-unconventional-assign-operator + misc-uniqueptr-reset-release + misc-unused-alias-decls + misc-unused-parameters + misc-unused-using-decls + 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-uncaught-exceptions + modernize-use-using + mpi-buffer-deref + mpi-type-mismatch + objc-avoid-nserror-init + objc-avoid-spinlock + objc-forbidden-subclassing + objc-property-declaration + performance-faster-string-find + performance-for-range-copy + performance-implicit-conversion-in-loop + performance-inefficient-algorithm + performance-inefficient-string-concatenation + performance-inefficient-vector-operation + performance-move-const-arg + performance-move-constructor-init + performance-noexcept-move-constructor + performance-type-promotion-in-math-fn + performance-unnecessary-copy-initialization + performance-unnecessary-value-param + portability-simd-intrinsics + 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-conversion + 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-simplify-subscript-expr + readability-static-accessed-through-instance + readability-static-definition-in-anonymous-namespace + readability-string-compare + readability-uniqueptr-delete-release + zircon-temporary-objects 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-definitions-in-headers.rst b/docs/clang-tidy/checks/misc-definitions-in-headers.rst new file mode 100644 index 000000000..1b6c2cd34 --- /dev/null +++ b/docs/clang-tidy/checks/misc-definitions-in-headers.rst @@ -0,0 +1,100 @@ +.. 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"; + constexpr int k = 1; + + // 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 f10() { return 0; } // OK: constexpr function implies inline. + +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-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-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-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..83c29bd75 --- /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 ``true``, + +- always ``false``, + +- always a constant (zero or one). + +Examples: + +.. 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-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-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-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..3dfeb299d --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-parameters.rst @@ -0,0 +1,42 @@ +.. title:: clang-tidy - misc-unused-parameters + +misc-unused-parameters +====================== + +Finds unused function parameters. Unused parameters may signify a bug in the +code (e.g. when a different parameter is used instead). The suggested fixes +either comment parameter name out or remove the parameter completely, if all +callers of the function are in the same translation unit and can be updated. + +The check is similar to the `-Wunused-parameter` compiler diagnostic and can be +used to prepare a codebase to enabling of that diagnostic. By default the check +is more permissive (see :option:`StrictMode`). + +.. code-block:: c++ + + void a(int i) { /*some code that doesn't use `i`*/ } + + // becomes + + void a(int /*i*/) { /*some code that doesn't use `i`*/ } + +.. code-block:: c++ + + static void staticFunctionA(int i); + static void staticFunctionA(int i) { /*some code that doesn't use `i`*/ } + + // becomes + + static void staticFunctionA() + static void staticFunctionA() { /*some code that doesn't use `i`*/ } + +Options +------- + +.. option:: StrictMode + + When zero (default value), the check will ignore trivially unused parameters, + i.e. when the corresponding function has an empty body (and in case of + constructors - no constructor initializers). When the function body is empty, + an unused parameter is unlikely to be unnoticed by a human reader, and + there's basically no place for a bug to hide. 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/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..e73b32034 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-shared.rst @@ -0,0 +1,50 @@ +.. 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`. + +.. option:: IgnoreMacros + + If set to non-zero, the check will not give warnings inside macros. Default + is `1`. 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..1961f05ed --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-unique.rst @@ -0,0 +1,50 @@ +.. 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`. + +.. option:: IgnoreMacros + + If set to non-zero, the check will not give warnings inside macros. Default + is `1`. 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..50674d423 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst @@ -0,0 +1,41 @@ +.. 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(), randomFunc); + +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. +Another thing is that the seeding quality of the suggested fix is quite poor: ``std::mt19937`` has an internal state of 624 32-bit integers, but is only seeded with a single integer. So if you require +higher quality randomness, you should consider seeding better, for example: + +.. code-block:: c++ + + std::shuffle(v.begin(), v.end(), []() { + std::mt19937::result_type seeds[std::mt19937::state_size]; + std::random_device device; + std::uniform_int_distribution dist; + std::generate(std::begin(seeds), std::end(seeds), [&] { return dist(device); }); + std::seed_seq seq(std::begin(seeds), std::end(seeds)); + return std::mt19937(seq); + }()); 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..a73e86eff --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-auto.rst @@ -0,0 +1,233 @@ +.. 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:: MinTypeNameLength + + If the option is set to non-zero (default `5`), the check will ignore type + names having a length less than the option value. The option affects + expressions only, not iterators. + Spaces between multi-lexeme type names (``long int``) are considered as one. + If ``RemoveStars`` option (see below) is set to non-zero, then ``*s`` in + the type are also counted as a part of the type name. + +.. code-block:: c++ + + // MinTypeNameLength = 0, RemoveStars=0 + + int a = static_cast(foo()); // ---> auto a = ... + // length(bool *) = 4 + bool *b = new bool; // ---> auto *b = ... + unsigned c = static_cast(foo()); // ---> auto c = ... + + // MinTypeNameLength = 5, RemoveStars=0 + + int a = static_cast(foo()); // ---> int a = ... + bool b = static_cast(foo()); // ---> bool b = ... + bool *pb = static_cast(foo()); // ---> bool *pb = ... + unsigned c = static_cast(foo()); // ---> auto c = ... + // length(long int) = 8 + long int d = static_cast(foo()); // ---> auto d = ... + + // MinTypeNameLength = 5, RemoveStars=1 + + int a = static_cast(foo()); // ---> int a = ... + // length(int * * ) = 5 + int **pa = static_cast(foo()); // ---> auto pa = ... + bool b = static_cast(foo()); // ---> bool b = ... + bool *pb = static_cast(foo()); // ---> auto pb = ... + unsigned c = static_cast(foo()); // ---> auto c = ... + long int d = static_cast(foo()); // ---> auto d = ... + +.. 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..792d3a4c3 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-bool-literals.rst @@ -0,0 +1,28 @@ +.. 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; + +Options +------- + +.. option:: IgnoreMacros + + If set to non-zero, the check will not give warnings inside macros. Default + is `1`. 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..533125e9b --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-emplace.rst @@ -0,0 +1,147 @@ +.. 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:: IgnoreImplicitConstructors + + When non-zero, the check will ignore implicitly constructed arguments of + ``push_back``, e.g. + + .. code-block:: c++ + + std::vector v; + v.push_back("a"); // Ignored when IgnoreImplicitConstructors is ``1``. + + Default is ``0``. + +.. 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..4b2368ce0 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-equals-default.rst @@ -0,0 +1,36 @@ +.. 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. + +Options +------- + +.. option:: IgnoreMacros + + If set to non-zero, the check will not give warnings inside macros. Default + is `1`. 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-uncaught-exceptions.rst b/docs/clang-tidy/checks/modernize-use-uncaught-exceptions.rst new file mode 100644 index 000000000..615f2e3f4 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-uncaught-exceptions.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - modernize-use-uncaught-exceptions + +modernize-use-uncaught-exceptions +==================================== + +This check will warn on calls to ``std::uncaught_exception`` and replace them +with calls to ``std::uncaught_exceptions``, since ``std::uncaught_exception`` +was deprecated in C++17. + +Below are a few examples of what kind of occurrences will be found and what +they will be replaced with. + +.. code-block:: c++ + + #define MACRO1 std::uncaught_exception + #define MACRO2 std::uncaught_exception + + int uncaught_exception() { + return 0; + } + + int main() { + int res; + + res = uncaught_exception(); + // No warning, since it is not the deprecated function from namespace std + + res = MACRO2(); + // Warning, but will not be replaced + + res = std::uncaught_exception(); + // Warning and replaced + + using std::uncaught_exception; + // Warning and replaced + + res = uncaught_exception(); + // Warning and replaced + } + +After applying the fixes the code will look like the following: + +.. code-block:: c++ + + #define MACRO1 std::uncaught_exception + #define MACRO2 std::uncaught_exception + + int uncaught_exception() { + return 0; + } + + int main() { + int res; + + res = uncaught_exception(); + + res = MACRO2(); + + res = std::uncaught_exceptions(); + + using std::uncaught_exceptions; + + res = uncaught_exceptions(); + } 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..1456a91f3 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-using.rst @@ -0,0 +1,34 @@ +.. 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. + +Options +------- + +.. option:: IgnoreMacros + + If set to non-zero, the check will not give warnings inside macros. Default + is `1`. 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/objc-avoid-nserror-init.rst b/docs/clang-tidy/checks/objc-avoid-nserror-init.rst new file mode 100644 index 000000000..265794c5c --- /dev/null +++ b/docs/clang-tidy/checks/objc-avoid-nserror-init.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - objc-avoid-nserror-init + +objc-avoid-nserror-init +======================= + +Finds improper initialization of ``NSError`` objects. + +According to Apple developer document, we should always use factory method +``errorWithDomain:code:userInfo:`` to create new NSError objects instead +of ``[NSError alloc] init]``. Otherwise it will lead to a warning message +during runtime. + +The corresponding information about ``NSError`` creation: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/CreateCustomizeNSError/CreateCustomizeNSError.html diff --git a/docs/clang-tidy/checks/objc-avoid-spinlock.rst b/docs/clang-tidy/checks/objc-avoid-spinlock.rst new file mode 100644 index 000000000..d1b11fcba --- /dev/null +++ b/docs/clang-tidy/checks/objc-avoid-spinlock.rst @@ -0,0 +1,15 @@ +.. title:: clang-tidy - objc-avoid-spinlock + +objc-avoid-spinlock +=================== + +Finds usages of ``OSSpinlock``, which is deprecated due to potential livelock +problems. + +This check will detect following function invocations: + +- ``OSSpinlockLock`` +- ``OSSpinlockTry`` +- ``OSSpinlockUnlock`` + +The corresponding information about the problem of ``OSSpinlock``: https://blog.postmates.com/why-spinlocks-are-bad-on-ios-b69fc5221058 diff --git a/docs/clang-tidy/checks/objc-forbidden-subclassing.rst b/docs/clang-tidy/checks/objc-forbidden-subclassing.rst new file mode 100644 index 000000000..4bb023cda --- /dev/null +++ b/docs/clang-tidy/checks/objc-forbidden-subclassing.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - objc-forbidden-subclassing + +objc-forbidden-subclassing +========================== + +Finds Objective-C classes which are subclasses of classes which are not designed +to be subclassed. + +By default, includes a list of Objective-C classes which are publicly documented +as not supporting subclassing. + +.. note:: + + Instead of using this check, for code under your control, you should add + ``__attribute__((objc_subclassing_restricted))`` before your ``@interface`` + declarations to ensure the compiler prevents others from subclassing your + Objective-C classes. + See https://clang.llvm.org/docs/AttributeReference.html#objc-subclassing-restricted + +Options +------- + +.. option:: ForbiddenSuperClassNames + + Semicolon-separated list of names of Objective-C classes which + do not support subclassing. + + Defaults to `ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView`. diff --git a/docs/clang-tidy/checks/objc-property-declaration.rst b/docs/clang-tidy/checks/objc-property-declaration.rst new file mode 100644 index 000000000..f851056b4 --- /dev/null +++ b/docs/clang-tidy/checks/objc-property-declaration.rst @@ -0,0 +1,69 @@ +.. title:: clang-tidy - objc-property-declaration + +objc-property-declaration +========================= + +Finds property declarations in Objective-C files that do not follow the pattern +of property names in Apple's programming guide. The property name should be +in the format of Lower Camel Case. + +For code: + +.. code-block:: objc + + @property(nonatomic, assign) int LowerCamelCase; + +The fix will be: + +.. code-block:: objc + + @property(nonatomic, assign) int lowerCamelCase; + +The check will only fix 'CamelCase' to 'camelCase'. In some other cases we will +only provide warning messages since the property name could be complicated. +Users will need to come up with a proper name by their own. + +This check also accepts special acronyms as prefixes or suffixes. Such prefixes or suffixes +will suppress the Lower Camel Case check according to the guide: +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-1002931-BBCFHEAB + +For a full list of well-known acronyms: +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/APIAbbreviations.html#//apple_ref/doc/uid/20001285-BCIHCGAE + +The corresponding style rule: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-1001757 + +The check will also accept property declared in category with a prefix of +lowercase letters followed by a '_' to avoid naming conflict. For example: + +.. code-block:: objc + + @property(nonatomic, assign) int abc_lowerCamelCase; + +The corresponding style rule: https://developer.apple.com/library/content/qa/qa1908/_index.html + + +Options +------- + +.. option:: Acronyms + + Semicolon-separated list of custom acronyms that can be used as a prefix + or a suffix of property names. + + By default, appends to the list of default acronyms ( + ``IncludeDefaultAcronyms`` set to ``1``). + If ``IncludeDefaultAcronyms`` is set to ``0``, instead replaces the + default list of acronyms. + +.. option:: IncludeDefaultAcronyms + + Integer value (defaults to ``1``) to control whether the default + acronyms are included in the list of acronyms. + + If set to ``1``, the value in ``Acronyms`` is appended to the + default list of acronyms: + + ``ACL;API;ARGB;ASCII;BGRA;CMYK;DNS;FPS;FTP;GIF;GPS;HD;HDR;HTML;HTTP;HTTPS;HUD;ID;JPG;JS;LAN;LZW;MDNS;MIDI;OS;PDF;PIN;PNG;POI;PSTN;PTR;QA;QOS;RGB;RGBA;RGBX;ROM;RPC;RTF;RTL;SDK;SSO;TCP;TIFF;TTS;UI;URI;URL;VC;VOIP;VPN;VR;WAN;XML``. + + If set to ``0``, the value in ``Acronyms`` replaces the default list + of acronyms. 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..280e7c046 --- /dev/null +++ b/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst @@ -0,0 +1,12 @@ +:orphan: + +.. title:: clang-tidy - performance-implicit-cast-in-loop +.. meta:: + :http-equiv=refresh: 5;URL=performance-implicit-conversion-in-loop.html + +performance-implicit-cast-in-loop +================================= + +This check has been renamed to `performance-implicit-conversion-in-loop +`_. + diff --git a/docs/clang-tidy/checks/performance-implicit-conversion-in-loop.rst b/docs/clang-tidy/checks/performance-implicit-conversion-in-loop.rst new file mode 100644 index 000000000..14e4d31f9 --- /dev/null +++ b/docs/clang-tidy/checks/performance-implicit-conversion-in-loop.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - performance-implicit-conversion-in-loop + +performance-implicit-conversion-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 conversion happens, 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 conversion, 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-algorithm.rst b/docs/clang-tidy/checks/performance-inefficient-algorithm.rst new file mode 100644 index 000000000..700274493 --- /dev/null +++ b/docs/clang-tidy/checks/performance-inefficient-algorithm.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - performance-inefficient-algorithm + +performance-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/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-move-const-arg.rst b/docs/clang-tidy/checks/performance-move-const-arg.rst new file mode 100644 index 000000000..39e1ca4af --- /dev/null +++ b/docs/clang-tidy/checks/performance-move-const-arg.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - performance-move-const-arg + +performance-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 + +Options +------- + +.. option:: CheckTriviallyCopyableMove + + If non-zero, enables detection of trivially copyable types that do not + have a move constructor. Default is non-zero. diff --git a/docs/clang-tidy/checks/performance-move-constructor-init.rst b/docs/clang-tidy/checks/performance-move-constructor-init.rst new file mode 100644 index 000000000..a193b9ee8 --- /dev/null +++ b/docs/clang-tidy/checks/performance-move-constructor-init.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - performance-move-constructor-init + +performance-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/performance-noexcept-move-constructor.rst b/docs/clang-tidy/checks/performance-noexcept-move-constructor.rst new file mode 100644 index 000000000..05f1d85f1 --- /dev/null +++ b/docs/clang-tidy/checks/performance-noexcept-move-constructor.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - performance-noexcept-move-constructor + +performance-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/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/portability-simd-intrinsics.rst b/docs/clang-tidy/checks/portability-simd-intrinsics.rst new file mode 100644 index 000000000..2cd9d9f75 --- /dev/null +++ b/docs/clang-tidy/checks/portability-simd-intrinsics.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - portability-simd-intrinsics + +portability-simd-intrinsics +=========================== + +Finds SIMD intrinsics calls and suggests ``std::experimental::simd`` (`P0214`_) +alternatives. + +If the option ``Suggest`` is set to non-zero, for + +.. code-block:: c++ + + _mm_add_epi32(a, b); // x86 + vec_add(a, b); // Power + +the check suggests an alternative: ``operator+`` on ``std::experimental::simd`` +objects. + +Otherwise, it just complains the intrinsics are non-portable (and there are +`P0214`_ alternatives). + +Many architectures provide SIMD operations (e.g. x86 SSE/AVX, Power AltiVec/VSX, +ARM NEON). It is common that SIMD code implementing the same algorithm, is +written in multiple target-dispatching pieces to optimize for different +architectures or micro-architectures. + +The C++ standard proposal `P0214`_ and its extensions cover many common SIMD +operations. By migrating from target-dependent intrinsics to `P0214`_ +operations, the SIMD code can be simplified and pieces for different targets can +be unified. + +Refer to `P0214`_ for introduction and motivation for the data-parallel standard +library. + +Options +------- + +.. option:: Suggest + + If this option is set to non-zero (default is `0`), the check will suggest + `P0214`_ alternatives, otherwise it only points out the intrinsic function is + non-portable. + +.. option:: Std + + The namespace used to suggest `P0214`_ alternatives. If not specified, `std::` + for `-std=c++2a` and `std::experimental::` for `-std=c++11`. + +.. _P0214: http://wg21.link/p0214 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..3360fbd5f --- /dev/null +++ b/docs/clang-tidy/checks/readability-function-size.rst @@ -0,0 +1,45 @@ +.. 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). + +.. option:: VariableThreshold + + Flag functions exceeding this number of variables declared in the body. + The default is `-1` (ignore the number of variables). + Please note that function parameters and variables declared in lambdas, + GNU Statement Expressions, and nested class inline functions are not counted. 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..a6a349268 --- /dev/null +++ b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst @@ -0,0 +1,11 @@ +:orphan: + +.. title:: clang-tidy - readability-implicit-bool-cast +.. meta:: + :http-equiv=refresh: 5;URL=readability-implicit-bool-conversion.html + +readability-implicit-bool-cast +============================== + +This check has been renamed to `readability-implicit-bool-conversion +`_. diff --git a/docs/clang-tidy/checks/readability-implicit-bool-conversion.rst b/docs/clang-tidy/checks/readability-implicit-bool-conversion.rst new file mode 100644 index 000000000..64f3f7367 --- /dev/null +++ b/docs/clang-tidy/checks/readability-implicit-bool-conversion.rst @@ -0,0 +1,132 @@ +.. title:: clang-tidy - readability-implicit-bool-conversion + +readability-implicit-bool-conversion +==================================== + +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`` conversion: + +.. code-block:: c++ + + class Foo { + int m_foo; + + public: + void setFoo(bool foo) { m_foo = foo; } // warning: implicit conversion bool -> int + int getFoo() { return m_foo; } + }; + + void use(Foo& foo) { + bool value = foo.getFoo(); // warning: implicit conversion 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 conversion 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 conversions 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 conversion to bool, the proposed replacement + with comparison is simplified: + + - ``if (!pointer)`` is changed to ``if (pointer == nullptr)``, + +- in case of conversions from bool to other built-in types, an explicit + ``static_cast`` is proposed to make it clear that a conversion is taking + place: + + - ``int integer = boolean;`` is changed to + ``int integer = static_cast(boolean);``, + +- if the conversion 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 conversion to pointer is detected, + +- instead of ``nullptr`` literal, ``0`` is proposed as replacement. + +Occurrences of implicit conversions inside macros and template instantiations +are deliberately ignored, as it is not clear how to deal with such cases. + +Options +------- + +.. option:: AllowIntegerConditions + + When non-zero, the check will allow conditional integer conversions. Default + is `0`. + +.. option:: AllowPointerConditions + + When non-zero, the check will allow conditional pointer conversions. 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..a47bb41b1 --- /dev/null +++ b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst @@ -0,0 +1,63 @@ +.. 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 + +One name is also allowed to be a case-insensitive prefix/suffix of the other: + +.. code-block:: c++ + + void foo(int count); + void foo(int count_input) { // no warning + int count = adjustCount(count_input); + } + +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. + +.. option:: IgnoreMacros + + If this option is set to non-zero (default is `1`), the check will not warn + about names declared inside macros. + +.. option:: Strict + + If this option is set to non-zero (default is `0`), then names must match + exactly (or be absent). 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..8ce7aacce --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-declaration.rst @@ -0,0 +1,37 @@ +.. 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. + +Options +------- + +.. option:: IgnoreMacros + + If set to non-zero, the check will not give warnings inside macros. Default + is `1`. 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..3fc77c5f5 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - readability-redundant-smartptr-get + +readability-redundant-smartptr-get +================================== + +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 + if (ptr.get() == nullptr) ... => if (ptr == nullptr) ... + 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-simplify-subscript-expr.rst b/docs/clang-tidy/checks/readability-simplify-subscript-expr.rst new file mode 100644 index 000000000..f3f44bedc --- /dev/null +++ b/docs/clang-tidy/checks/readability-simplify-subscript-expr.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - readability-simplify-subscript-expr + +readability-simplify-subscript-expr +=================================== + +This check simplifies subscript expressions. Currently this covers calling +``.data()`` and immediately doing an array subscript operation to obtain a +single element, in which case simply calling ``operator[]`` suffice. + +Examples: + +.. code-block:: c++ + + std::string s = ...; + char c = s.data()[i]; // char c = s[i]; + +Options +------- + +.. option:: Types + + The list of type(s) that triggers this check. Default is + `::std::basic_string;::std::basic_string_view;::std::vector;::std::array` diff --git a/docs/clang-tidy/checks/readability-static-accessed-through-instance.rst b/docs/clang-tidy/checks/readability-static-accessed-through-instance.rst new file mode 100644 index 000000000..879b87c2f --- /dev/null +++ b/docs/clang-tidy/checks/readability-static-accessed-through-instance.rst @@ -0,0 +1,31 @@ +.. title:: clang-tidy - readability-static-accessed-through-instance + +readability-static-accessed-through-instance +============================================ + +Checks for member expressions that access static members through instances, and +replaces them with uses of the appropriate qualified-id. + +Example: + +The following code: + +.. code-block:: c++ + + struct C { + static void foo(); + static int x; + }; + + C *c1 = new C(); + c1->foo(); + c1->x; + +is changed to: + +.. code-block:: c++ + + C *c1 = new C(); + C::foo(); + C::x; + 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-string-compare.rst b/docs/clang-tidy/checks/readability-string-compare.rst new file mode 100644 index 000000000..58d01d553 --- /dev/null +++ b/docs/clang-tidy/checks/readability-string-compare.rst @@ -0,0 +1,54 @@ +.. title:: clang-tidy - readability-string-compare + +readability-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/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/checks/zircon-temporary-objects.rst b/docs/clang-tidy/checks/zircon-temporary-objects.rst new file mode 100644 index 000000000..7491f77e4 --- /dev/null +++ b/docs/clang-tidy/checks/zircon-temporary-objects.rst @@ -0,0 +1,53 @@ +.. title:: clang-tidy - zircon-temporary-objects + +zircon-temporary-objects +======================== + +Warns on construction of specific temporary objects in the Zircon kernel. +If the object should be flagged, If the object should be flagged, the fully +qualified type name must be explicitly passed to the check. + +For example, given the list of classes "Foo" and "NS::Bar", all of the +following will trigger the warning: + +.. code-block:: c++ + + Foo(); + Foo F = Foo(); + func(Foo()); + + namespace NS { + + Bar(); + + } + +With the same list, the following will not trigger the warning: + +.. code-block:: c++ + + Foo F; // Non-temporary construction okay + Foo F(param); // Non-temporary construction okay + Foo *F = new Foo(); // New construction okay + + Bar(); // Not NS::Bar, so okay + NS::Bar B; // Non-temporary construction okay + +Note that objects must be explicitly specified in order to be flagged, +and so objects that inherit a specified object will not be flagged. + +This check matches temporary objects without regard for inheritance and so a +prohibited base class type does not similarly prohibit derived class types. + +.. code-block:: c++ + + class Derived : Foo {} // Derived is not explicitly disallowed + Derived(); // and so temporary construction is okay + +Options +------- + +.. option:: Names + + A semi-colon-separated list of fully-qualified names of C++ classes that + should not be constructed as temporaries. Default is empty. diff --git a/docs/clang-tidy/index.rst b/docs/clang-tidy/index.rst new file mode 100644 index 000000000..ec6c24ff5 --- /dev/null +++ b/docs/clang-tidy/index.rst @@ -0,0 +1,810 @@ +========== +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. +``fuchsia-`` Checks related to Fuchsia coding conventions. +``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). +``objc-`` Checks related to Objective-C coding conventions. +``performance-`` Checks that target performance-related issues. +``portability-`` Checks that target portability-related issues that don't + relate to any particular coding style. +``readability-`` Checks that target readability-related issues that don't + relate to any particular coding style. +``zircon-`` Checks related to Zircon kernel coding conventions. +====================== ========================================================= + +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: + + -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. + -store-check-profile= - + By default reports are printed in tabulated + format to stderr. When this option is passed, + these per-TU profiles are instead stored as JSON. + -system-headers - Display the errors from system headers. + -vfsoverlay= - + Overlay the virtual filesystem described by file + over the real file system. + -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: '' + FormatStyle: none + User: user + CheckOptions: + - key: some-check.SomeOption + value: 'some value' + ... + +:program:`clang-tidy` diagnostics are intended to call out code that does +not adhere to a coding standard, or is otherwise problematic in some way. +However, if it is known that the code is correct, the check-specific ways +to silence the diagnostics could be used, if they are available (e.g. +bugprone-use-after-move can be silenced by re-initializing the variable after +it has been moved out, misc-string-integer-assignment can be suppressed by +explicitly casting the integer to char, readability-implicit-bool-conversion +can also be suppressed by using explicit casts, etc.). If they are not +available or if changing the semantics of the code is not desired, +the ``NOLINT`` or ``NOLINTNEXTLINE`` comments can be used instead. For example: + +.. code-block:: c++ + + class Foo + { + // Silent all the diagnostics for the line + Foo(int param); // NOLINT + + // Silent only the specified checks for the line + Foo(double param); // NOLINT(google-explicit-constructor, google-runtime-int) + + // Silent only the specified diagnostics for the next line + // NOLINTNEXTLINE(google-explicit-constructor, google-runtime-int) + Foo(bool param); + }; + +The formal syntax of ``NOLINT``/``NOLINTNEXTLINE`` is the following: + +.. parsed-literal:: + + lint-comment: + lint-command + lint-command lint-args + + lint-args: + **(** check-name-list **)** + + check-name-list: + *check-name* + check-name-list **,** *check-name* + + lint-command: + **NOLINT** + **NOLINTNEXTLINE** + +Note that whitespaces between ``NOLINT``/``NOLINTNEXTLINE`` and the opening +parenthesis are not allowed (in this case the comment will be treated just as +``NOLINT``/``NOLINTNEXTLINE``), whereas in check names list (inside +the parenthesis) whitespaces can be used and will be ignored. + +.. _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 + ... + |-- objc/ # Objective-C clang-tidy module. + |-+ + |-- ObjCTidyModule.cpp + |-- ObjCTidyModule.h + ... + |-- tool/ # Sources of the clang-tidy binary. + ... + test/clang-tidy/ # Integration tests. + ... + unittests/clang-tidy/ # Unit tests. + |-- ClangTidyTest.h + |-- GoogleModuleTest.cpp + |-- LLVMModuleTest.cpp + |-- ObjCModuleTest.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; + } + +To check more than one scenario in the same test file use +``-check-suffix=SUFFIX-NAME`` on ``check_clang_tidy.py`` command line. +With ``-check-suffix=SUFFIX-NAME`` you need to replace your ``CHECK-*`` +directives with ``CHECK-MESSAGES-SUFFIX-NAME`` and ``CHECK-FIXES-SUFFIX-NAME``. + +Here's an example: + +.. code-block:: c++ + + // RUN: %check_clang_tidy -check-suffix=USING-A %s misc-unused-using-decls %t -- -- -DUSING_A + // RUN: %check_clang_tidy -check-suffix=USING-B %s misc-unused-using-decls %t -- -- -DUSING_B + // RUN: %check_clang_tidy %s misc-unused-using-decls %t + ... + // CHECK-MESSAGES-USING-A: :[[@LINE-8]]:10: warning: using decl 'A' {{.*}} + // CHECK-MESSAGES-USING-B: :[[@LINE-7]]:10: warning: using decl 'B' {{.*}} + // CHECK-MESSAGES: :[[@LINE-6]]:10: warning: using decl 'C' {{.*}} + // CHECK-FIXES-USING-A-NOT: using a::A;$ + // CHECK-FIXES-USING-B-NOT: using a::B;$ + // CHECK-FIXES-NOT: using a::C;$ + + +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. + + +On checks profiling +------------------- + +:program:`clang-tidy` can collect per-check profiling info, and output it +for each processed source file (translation unit). + +To enable profiling info collection, use the ``-enable-check-profile`` argument. +The timings will be output to ``stderr`` as a table. Example output: + +.. code-block:: console + + $ clang-tidy -enable-check-profile -checks=-*,readability-function-size source.cpp + ===-------------------------------------------------------------------------=== + clang-tidy checks profiling + ===-------------------------------------------------------------------------=== + Total Execution Time: 1.0282 seconds (1.0258 wall clock) + + ---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name --- + 0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) readability-function-size + 0.9136 (100.0%) 0.1146 (100.0%) 1.0282 (100.0%) 1.0258 (100.0%) Total + +It can also store that data as JSON files for further processing. Example output: + +.. code-block:: console + + $ clang-tidy -enable-check-profile -store-check-profile=. -checks=-*,readability-function-size source.cpp + $ # Note that there won't be timings table printed to the console. + $ ls /tmp/out/ + 20180516161318717446360-source.cpp.json + $ cat 20180516161318717446360-source.cpp.json + { + "file": "/path/to/source.cpp", + "timestamp": "2018-05-16 16:13:18.717446360", + "profile": { + "time.clang-tidy.readability-function-size.wall": 1.0421266555786133e+00, + "time.clang-tidy.readability-function-size.user": 9.2088400000005421e-01, + "time.clang-tidy.readability-function-size.sys": 1.2418899999999974e-01 + } + } + +There is only one argument that controls profile storage: + +* ``-store-check-profile=`` + + By default reports are printed in tabulated format to stderr. When this option + is passed, these per-TU profiles are instead stored as JSON. + If the prefix is not an absolute path, it is considered to be relative to the + directory from where you have run :program:`clang-tidy`. All ``.`` and ``..`` + patterns in the path are collapsed, and symlinks are resolved. + + Example: + Let's suppose you have a source file named ``example.cpp``, located in the + ``/source`` directory. Only the input filename is used, not the full path + to the source file. Additionally, it is prefixed with the current timestamp. + + * If you specify ``-store-check-profile=/tmp``, then the profile will be saved + to ``/tmp/-example.cpp.json`` + + * If you run :program:`clang-tidy` from within ``/foo`` directory, and specify + ``-store-check-profile=.``, then the profile will still be saved to + ``/foo/-example.cpp.json`` diff --git a/docs/clangd.rst b/docs/clangd.rst new file mode 100644 index 000000000..97736efdf --- /dev/null +++ b/docs/clangd.rst @@ -0,0 +1,123 @@ +============ +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 `installing Clangd`_ or +`building Clangd`_, then open Visual Studio Code in the clangd-vscode folder and +launch the extension. + +Installing Clangd +================== + +Packages are available for debian-based distributions, see the `LLVM packages +page `_. :program:`Clangd` is included in the +`clang-tools` package. +However, it is a good idea to check your distribution's packaging system first +as it might already be available. + +Otherwise, you can install :program:`Clangd` by `building Clangd`_ first. + +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` or as an +extension to the protocol. + ++-------------------------------------+------------+----------+ +| C/C++ Editor feature | LSP | Clangd | ++=====================================+============+==========+ +| Formatting | Yes | Yes | ++-------------------------------------+------------+----------+ +| Completion | Yes | Yes | ++-------------------------------------+------------+----------+ +| Diagnostics | Yes | Yes | ++-------------------------------------+------------+----------+ +| Fix-its | Yes | Yes | ++-------------------------------------+------------+----------+ +| Go to Definition | Yes | Yes | ++-------------------------------------+------------+----------+ +| Signature Help | Yes | Yes | ++-------------------------------------+------------+----------+ +| Document Highlights | Yes | Yes | ++-------------------------------------+------------+----------+ +| Rename | Yes | Yes | ++-------------------------------------+------------+----------+ +| Source hover | Yes | Yes | ++-------------------------------------+------------+----------+ +| Find References | Yes | No | ++-------------------------------------+------------+----------+ +| Code Lens | Yes | No | ++-------------------------------------+------------+----------+ +| Document Symbols | Yes | Yes | ++-------------------------------------+------------+----------+ +| Workspace Symbols | 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..5ac182169 --- /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 = '7' +# The full version, including alpha/beta/rc tags. +release = '7' + +# 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..880958893 --- /dev/null +++ b/docs/doxygen.cfg.in @@ -0,0 +1,2305 @@ +# 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@/../change-namespace \ + @abs_srcdir@/../clang-apply-replacements \ + @abs_srcdir@/../clang-doc \ + @abs_srcdir@/../clang-move \ + @abs_srcdir@/../clang-query \ + @abs_srcdir@/../clang-reorder-fields \ + @abs_srcdir@/../clang-tidy \ + @abs_srcdir@/../clangd \ + @abs_srcdir@/../include-fixer \ + @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 +# , /