Index: clang-tools-extra/clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -11,6 +11,7 @@ ContainerSizeEmptyCheck.cpp ConvertMemberFunctionsToStatic.cpp DeleteNullPointerCheck.cpp + DuplicateIncludeCheck.cpp ElseAfterReturnCheck.cpp FunctionCognitiveComplexityCheck.cpp FunctionSizeCheck.cpp Index: clang-tools-extra/clang-tidy/readability/DuplicateIncludeCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/readability/DuplicateIncludeCheck.h @@ -0,0 +1,35 @@ +//===--- DuplicateIncludeCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DUPLICATE_INCLUDE_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DUPLICATE_INCLUDE_CHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Find and remove duplicate #include directives. +/// +/// Only consecutive include directives without any other preprocessor +/// directives between them are analyzed. +class DuplicateIncludeCheck : public ClangTidyCheck { +public: + DuplicateIncludeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DUPLICATE_INCLUDE_CHECK_H Index: clang-tools-extra/clang-tidy/readability/DuplicateIncludeCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/readability/DuplicateIncludeCheck.cpp @@ -0,0 +1,103 @@ +//===--- DuplicateIncludeCheck.cpp - clang-tidy ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "DuplicateIncludeCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" +#include +#include +#include + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +SourceLocation advanceBeyondCurrentLine(const SourceManager &SM, + SourceLocation Start, int Offset) { + const FileID Id = SM.getFileID(Start); + const unsigned LineNumber = SM.getSpellingLineNumber(Start); + while (SM.getFileID(Start) == Id && + SM.getSpellingLineNumber(Start.getLocWithOffset(Offset)) == + LineNumber) { + Start = Start.getLocWithOffset(Offset); + } + return Start; +} + +class DuplicateIncludeCallbacks : public PPCallbacks { +public: + DuplicateIncludeCallbacks(DuplicateIncludeCheck &Check, + const 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 MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override; + + void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD, + const MacroDirective *Undef) override; + +private: + std::vector Files; + DuplicateIncludeCheck &Check; + const SourceManager &SM; +}; + +void DuplicateIncludeCallbacks::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 (!SM.isInMainFile(HashLoc)) { + return; + } + + if (std::find(Files.cbegin(), Files.cend(), FileName) != Files.end()) { + const auto Start = + advanceBeyondCurrentLine(SM, HashLoc, -1).getLocWithOffset(-1); + const auto End = advanceBeyondCurrentLine(SM, FilenameRange.getEnd(), 1); + Check.diag(HashLoc, "duplicate include") + << FixItHint::CreateRemoval(SourceRange(Start, End)); + } else { + Files.push_back(FileName); + } +} + +void DuplicateIncludeCallbacks::MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) { + if (SM.isInMainFile(MacroNameTok.getLocation())) { + Files.clear(); + } +} + +void DuplicateIncludeCallbacks::MacroUndefined(const Token &MacroNameTok, + const MacroDefinition &MD, + const MacroDirective *Undef) { + if (SM.isInMainFile(MacroNameTok.getLocation())) { + Files.clear(); + } +} + +} // namespace + +void DuplicateIncludeCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + PP->addPPCallbacks(std::make_unique(*this, SM)); +} + +} // namespace readability +} // namespace tidy +} // namespace clang Index: clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -16,6 +16,7 @@ #include "ContainerSizeEmptyCheck.h" #include "ConvertMemberFunctionsToStatic.h" #include "DeleteNullPointerCheck.h" +#include "DuplicateIncludeCheck.h" #include "ElseAfterReturnCheck.h" #include "FunctionCognitiveComplexityCheck.h" #include "FunctionSizeCheck.h" @@ -71,6 +72,8 @@ "readability-convert-member-functions-to-static"); CheckFactories.registerCheck( "readability-delete-null-pointer"); + CheckFactories.registerCheck( + "readability-duplicate-include"); CheckFactories.registerCheck( "readability-else-after-return"); CheckFactories.registerCheck( Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -111,6 +111,11 @@ Finds cases where code could use ``data()`` rather than the address of the element at index 0 in a container. +- New :doc:`readability-duplicate-include + ` check. + + Finds duplicate includes, e.g. files included more than once. + - New :doc:`readability-identifier-length ` check. Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -294,6 +294,7 @@ `readability-container-size-empty `_, "Yes" `readability-convert-member-functions-to-static `_, `readability-delete-null-pointer `_, "Yes" + `readability-duplicate-include `_, "Yes" `readability-else-after-return `_, "Yes" `readability-function-cognitive-complexity `_, `readability-function-size `_, Index: clang-tools-extra/docs/clang-tidy/checks/readability-duplicate-include.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/readability-duplicate-include.rst @@ -0,0 +1,48 @@ +.. title:: clang-tidy - readability-duplicate-include + +readability-duplicate-include +============================= + +Looks for duplicate includes and removes them. The check maintains a list of +included files and looks for duplicates. If a macro is defined or undefined +then the list of included files is cleared. + +A common LLVM preprocessor tactic is to define a macro that controls an +included file and then include it multiple times in order to achieve different +effects. Resetting the list of seen includes every time a macro is defined +or undefined prevents a false positive from that use case. + +Examples: + +.. code-block:: c++ + + #include + #include + #include + +becomes + +.. code-block:: c++ + + #include + #include + +Because of the intervening macro definitions, this code remains unchanged: + +.. code-block:: c++ + + class LangOptionsBase { + public: + // Define simple language options (with no accessors). + #define LANGOPT(Name, Bits, Default, Description) unsigned Name : Bits; + #define ENUM_LANGOPT(Name, Type, Bits, Default, Description) + #include "clang/Basic/LangOptions.def" + + protected: + // Define language options of enumeration type. These are private, and will + // have accessors (below). + #define LANGOPT(Name, Bits, Default, Description) + #define ENUM_LANGOPT(Name, Type, Bits, Default, Description) \ + unsigned Name : Bits; + #include "clang/Basic/LangOptions.def" + }; Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/iostream =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/iostream @@ -0,0 +1 @@ +// empty file used by readability-duplicate-include.cpp Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/readability-duplicate-include.h =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/readability-duplicate-include.h @@ -0,0 +1 @@ +// empty file used by readability-duplicate-include.cpp Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/string.h =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/string.h @@ -0,0 +1 @@ +// empty file used by readability-duplicate-include.cpp Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/sys/types.h =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/sys/types.h @@ -0,0 +1 @@ +// empty file used by readability-duplicate-include.cpp Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/types.h =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-duplicate-include/types.h @@ -0,0 +1 @@ +// empty file used by readability-duplicate-include.cpp Index: clang-tools-extra/test/clang-tidy/checkers/readability-duplicate-include.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/readability-duplicate-include.cpp @@ -0,0 +1,72 @@ +// RUN: %check_clang_tidy -std=c++98-or-later %s readability-duplicate-include %t -- -- -I%S/Inputs/readability-duplicate-include + +int a; +#include +int b; +#include +int c; +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: duplicate include [readability-duplicate-include] +// CHECK-FIXES: {{^int a;$}} +// CHECK-FIXES-NEXT: {{^#include $}} +// CHECK-FIXES-NEXT: {{^int b;$}} +// CHECK-FIXES-NEXT: {{^int c;$}} + +int d; +#include +int e; +#include // extra stuff that will also be removed +int f; +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: {{.*}} +// CHECK-FIXES: {{^int d;$}} +// CHECK-FIXES-NEXT: {{^#include $}} +// CHECK-FIXES-NEXT: {{^int e;$}} +// CHECK-FIXES-NEXT: {{^int f;$}} + +int g; +#include "readability-duplicate-include.h" +int h; +#include "readability-duplicate-include.h" +int i; +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: {{.*}} +// CHECK-FIXES: {{^int g;$}} +// CHECK-FIXES-NEXT: {{^#include "readability-duplicate-include.h"$}} +// CHECK-FIXES-NEXT: {{^int h;$}} +// CHECK-FIXES-NEXT: {{^int i;$}} + +#include "types.h" + +int j; +#include +int k; +#include +int l; +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: {{.*}} +// CHECK-FIXES: {{^int j;$}} +// CHECK-FIXES-NEXT: {{^#include $}} +// CHECK-FIXES-NEXT: {{^int k;$}} +// CHECK-FIXES-NEXT: {{^int l;$}} + +int m; + # include // lots of space +int n; +// CHECK-MESSAGES: :[[@LINE-2]]:9: warning: {{.*}} +// CHECK-FIXES: {{^int m;$}} +// CHECK-FIXES-NEXT: {{^int n;$}} + +// defining a macro in the main file resets the included file cache +#define ARBITRARY_MACRO +int o; +#include +int p; +// CHECK-FIXES: {{^int o;$}} +// CHECK-FIXES-NEXT: {{^#include $}} +// CHECK-FIXES-NEXT: {{^int p;$}} + +// undefining a macro resets the cache +#undef ARBITRARY_MACRO +int q; +#include +int r; +// CHECK-FIXES: {{^int q;$}} +// CHECK-FIXES-NEXT: {{^#include $}} +// CHECK-FIXES-NEXT: {{^int r;$}}