From 4f4fc861ff134b4b549fa4524d91bcdb45e01e07 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 14 Jun 2017 18:06:20 +0100 Subject: [PATCH 1/1] Import llvm-toolchain-4.0_4.0.1.orig-clang-tools-extra.tar.bz2 [dgit import orig llvm-toolchain-4.0_4.0.1.orig-clang-tools-extra.tar.bz2] --- .arcconfig | 4 + .gitignore | 32 + CMakeLists.txt | 29 + CODE_OWNERS.TXT | 21 + LICENSE.TXT | 62 + README.txt | 22 + change-namespace/CMakeLists.txt | 19 + change-namespace/ChangeNamespace.cpp | 906 +++++++ change-namespace/ChangeNamespace.h | 172 ++ change-namespace/tool/CMakeLists.txt | 23 + .../tool/ClangChangeNamespace.cpp | 114 + clang-apply-replacements/CMakeLists.txt | 19 + .../Tooling/ApplyReplacements.h | 155 ++ .../lib/Tooling/ApplyReplacements.cpp | 402 +++ clang-apply-replacements/tool/CMakeLists.txt | 17 + .../tool/ClangApplyReplacementsMain.cpp | 283 ++ clang-move/CMakeLists.txt | 21 + clang-move/ClangMove.cpp | 883 +++++++ clang-move/ClangMove.h | 229 ++ clang-move/HelperDeclRefGraph.cpp | 128 + clang-move/HelperDeclRefGraph.h | 99 + clang-move/tool/CMakeLists.txt | 17 + clang-move/tool/ClangMoveMain.cpp | 211 ++ clang-query/CMakeLists.txt | 18 + clang-query/Query.cpp | 146 ++ clang-query/Query.h | 136 + clang-query/QueryParser.cpp | 278 ++ clang-query/QueryParser.h | 71 + clang-query/QuerySession.h | 40 + clang-query/tool/CMakeLists.txt | 14 + clang-query/tool/ClangQuery.cpp | 116 + clang-rename/CMakeLists.txt | 18 + clang-rename/RenamingAction.cpp | 93 + clang-rename/RenamingAction.h | 48 + clang-rename/USRFinder.cpp | 212 ++ clang-rename/USRFinder.h | 84 + clang-rename/USRFindingAction.cpp | 226 ++ clang-rename/USRFindingAction.h | 53 + clang-rename/USRLocFinder.cpp | 167 ++ clang-rename/USRLocFinder.h | 35 + clang-rename/tool/CMakeLists.txt | 19 + clang-rename/tool/ClangRename.cpp | 231 ++ clang-rename/tool/clang-rename.el | 79 + clang-rename/tool/clang-rename.py | 61 + clang-reorder-fields/CMakeLists.txt | 15 + clang-reorder-fields/ReorderFieldsAction.cpp | 264 ++ clang-reorder-fields/ReorderFieldsAction.h | 47 + clang-reorder-fields/tool/CMakeLists.txt | 12 + .../tool/ClangReorderFields.cpp | 88 + clang-tidy-vs/.gitignore | 7 + clang-tidy-vs/CMakeLists.txt | 28 + clang-tidy-vs/ClangTidy.sln | 22 + clang-tidy-vs/ClangTidy/CategoryVerb.cs | 70 + clang-tidy-vs/ClangTidy/CheckDatabase.cs | 67 + clang-tidy-vs/ClangTidy/CheckTree.cs | 273 ++ clang-tidy-vs/ClangTidy/ClangTidy.csproj | 267 ++ clang-tidy-vs/ClangTidy/ClangTidy.vsct | 118 + .../ClangTidy/ClangTidyCheckAttribute.cs | 22 + .../ClangTidy/ClangTidyConfigParser.cs | 214 ++ .../ClangTidy/ClangTidyConfigurationPage.cs | 61 + clang-tidy-vs/ClangTidy/ClangTidyPackage.cs | 56 + .../ClangTidy/ClangTidyProperties.cs | 83 + .../ClangTidyPropertyGrid.Designer.cs | 119 + .../ClangTidy/ClangTidyPropertyGrid.cs | 208 ++ .../ClangTidy/ClangTidyPropertyGrid.resx | 123 + .../DynamicPropertyComponent.Designer.cs | 42 + .../ClangTidy/DynamicPropertyComponent.cs | 138 + .../ClangTidy/DynamicPropertyConverter.cs | 139 + .../ClangTidy/DynamicPropertyDescriptor.cs | 137 + .../ClangTidy/ForwardingPropertyDescriptor.cs | 191 ++ clang-tidy-vs/ClangTidy/GlobalSuppressions.cs | 11 + clang-tidy-vs/ClangTidy/Guids.cs | 12 + clang-tidy-vs/ClangTidy/PkgCmdID.cs | 7 + .../ClangTidy/Properties/AssemblyInfo.cs | 33 + clang-tidy-vs/ClangTidy/Resources.Designer.cs | 81 + clang-tidy-vs/ClangTidy/Resources.resx | 124 + .../ClangTidy/Resources/ClangTidyChecks.yaml | 321 +++ .../ClangTidy/Resources/Images_32bit.bmp | Bin 0 -> 5176 bytes clang-tidy-vs/ClangTidy/Resources/Package.ico | Bin 0 -> 1078 bytes clang-tidy-vs/ClangTidy/Utility.cs | 35 + clang-tidy-vs/ClangTidy/VSPackage.resx | 130 + clang-tidy-vs/ClangTidy/license.txt | 63 + clang-tidy-vs/ClangTidy/packages.config | 6 + .../ClangTidy/source.extension.vsixmanifest | 36 + clang-tidy-vs/README.txt | 17 + .../source.extension.vsixmanifest.in | 36 + clang-tidy/CMakeLists.txt | 41 + clang-tidy/ClangTidy.cpp | 580 +++++ clang-tidy/ClangTidy.h | 252 ++ clang-tidy/ClangTidyDiagnosticConsumer.cpp | 588 +++++ clang-tidy/ClangTidyDiagnosticConsumer.h | 263 ++ clang-tidy/ClangTidyModule.cpp | 39 + clang-tidy/ClangTidyModule.h | 99 + clang-tidy/ClangTidyModuleRegistry.h | 24 + clang-tidy/ClangTidyOptions.cpp | 339 +++ clang-tidy/ClangTidyOptions.h | 260 ++ clang-tidy/add_new_check.py | 310 +++ clang-tidy/boost/BoostTidyModule.cpp | 38 + clang-tidy/boost/CMakeLists.txt | 14 + clang-tidy/boost/UseToStringCheck.cpp | 73 + clang-tidy/boost/UseToStringCheck.h | 37 + clang-tidy/cert/CERTTidyModule.cpp | 84 + clang-tidy/cert/CMakeLists.txt | 24 + clang-tidy/cert/CommandProcessorCheck.cpp | 45 + clang-tidy/cert/CommandProcessorCheck.h | 38 + clang-tidy/cert/FloatLoopCounter.cpp | 35 + clang-tidy/cert/FloatLoopCounter.h | 37 + clang-tidy/cert/LICENSE.TXT | 22 + clang-tidy/cert/LimitedRandomnessCheck.cpp | 38 + clang-tidy/cert/LimitedRandomnessCheck.h | 38 + clang-tidy/cert/SetLongJmpCheck.cpp | 79 + clang-tidy/cert/SetLongJmpCheck.h | 38 + .../cert/StaticObjectExceptionCheck.cpp | 60 + clang-tidy/cert/StaticObjectExceptionCheck.h | 36 + clang-tidy/cert/StrToNumCheck.cpp | 235 ++ clang-tidy/cert/StrToNumCheck.h | 36 + clang-tidy/cert/ThrownExceptionTypeCheck.cpp | 41 + clang-tidy/cert/ThrownExceptionTypeCheck.h | 35 + clang-tidy/cert/VariadicFunctionDefCheck.cpp | 42 + clang-tidy/cert/VariadicFunctionDefCheck.h | 35 + clang-tidy/cppcoreguidelines/CMakeLists.txt | 29 + .../CppCoreGuidelinesTidyModule.cpp | 79 + .../InterfacesGlobalInitCheck.cpp | 59 + .../InterfacesGlobalInitCheck.h | 35 + .../cppcoreguidelines/NoMallocCheck.cpp | 62 + clang-tidy/cppcoreguidelines/NoMallocCheck.h | 44 + .../ProBoundsArrayToPointerDecayCheck.cpp | 80 + .../ProBoundsArrayToPointerDecayCheck.h | 35 + .../ProBoundsConstantArrayIndexCheck.cpp | 141 + .../ProBoundsConstantArrayIndexCheck.h | 42 + .../ProBoundsPointerArithmeticCheck.cpp | 59 + .../ProBoundsPointerArithmeticCheck.h | 37 + .../ProTypeConstCastCheck.cpp | 34 + .../cppcoreguidelines/ProTypeConstCastCheck.h | 35 + .../ProTypeCstyleCastCheck.cpp | 108 + .../ProTypeCstyleCastCheck.h | 36 + .../ProTypeMemberInitCheck.cpp | 482 ++++ .../ProTypeMemberInitCheck.h | 74 + .../ProTypeReinterpretCastCheck.cpp | 36 + .../ProTypeReinterpretCastCheck.h | 35 + .../ProTypeStaticCastDowncastCheck.cpp | 55 + .../ProTypeStaticCastDowncastCheck.h | 36 + .../ProTypeUnionAccessCheck.cpp | 38 + .../ProTypeUnionAccessCheck.h | 36 + .../cppcoreguidelines/ProTypeVarargCheck.cpp | 76 + .../cppcoreguidelines/ProTypeVarargCheck.h | 36 + clang-tidy/cppcoreguidelines/SlicingCheck.cpp | 136 + clang-tidy/cppcoreguidelines/SlicingCheck.h | 45 + .../SpecialMemberFunctionsCheck.cpp | 138 + .../SpecialMemberFunctionsCheck.h | 97 + clang-tidy/google/AvoidCStyleCastsCheck.cpp | 175 ++ clang-tidy/google/AvoidCStyleCastsCheck.h | 42 + clang-tidy/google/CMakeLists.txt | 27 + clang-tidy/google/DefaultArgumentsCheck.cpp | 36 + clang-tidy/google/DefaultArgumentsCheck.h | 34 + .../google/ExplicitConstructorCheck.cpp | 153 ++ clang-tidy/google/ExplicitConstructorCheck.h | 34 + clang-tidy/google/ExplicitMakePairCheck.cpp | 78 + clang-tidy/google/ExplicitMakePairCheck.h | 39 + .../google/GlobalNamesInHeadersCheck.cpp | 80 + clang-tidy/google/GlobalNamesInHeadersCheck.h | 47 + clang-tidy/google/GoogleTidyModule.cpp | 102 + clang-tidy/google/IntegerTypesCheck.cpp | 144 ++ clang-tidy/google/IntegerTypesCheck.h | 49 + clang-tidy/google/MemsetZeroLengthCheck.cpp | 96 + clang-tidy/google/MemsetZeroLengthCheck.h | 39 + clang-tidy/google/NonConstReferences.cpp | 149 ++ clang-tidy/google/NonConstReferences.h | 39 + clang-tidy/google/OverloadedUnaryAndCheck.cpp | 53 + clang-tidy/google/OverloadedUnaryAndCheck.h | 38 + .../google/StringReferenceMemberCheck.cpp | 51 + .../google/StringReferenceMemberCheck.h | 54 + clang-tidy/google/TodoCommentCheck.cpp | 66 + clang-tidy/google/TodoCommentCheck.h | 38 + .../google/UnnamedNamespaceInHeaderCheck.cpp | 63 + .../google/UnnamedNamespaceInHeaderCheck.h | 50 + .../google/UsingNamespaceDirectiveCheck.cpp | 46 + .../google/UsingNamespaceDirectiveCheck.h | 48 + clang-tidy/llvm/CMakeLists.txt | 18 + clang-tidy/llvm/HeaderGuardCheck.cpp | 68 + clang-tidy/llvm/HeaderGuardCheck.h | 46 + clang-tidy/llvm/IncludeOrderCheck.cpp | 179 ++ clang-tidy/llvm/IncludeOrderCheck.h | 33 + clang-tidy/llvm/LLVMTidyModule.cpp | 44 + clang-tidy/llvm/TwineLocalCheck.cpp | 63 + clang-tidy/llvm/TwineLocalCheck.h | 33 + clang-tidy/misc/ArgumentCommentCheck.cpp | 199 ++ clang-tidy/misc/ArgumentCommentCheck.h | 57 + clang-tidy/misc/AssertSideEffectCheck.cpp | 127 + clang-tidy/misc/AssertSideEffectCheck.h | 52 + .../BoolPointerImplicitConversionCheck.cpp | 73 + .../misc/BoolPointerImplicitConversionCheck.h | 42 + clang-tidy/misc/CMakeLists.txt | 59 + clang-tidy/misc/DanglingHandleCheck.cpp | 177 ++ clang-tidy/misc/DanglingHandleCheck.h | 43 + clang-tidy/misc/DefinitionsInHeadersCheck.cpp | 149 ++ clang-tidy/misc/DefinitionsInHeadersCheck.h | 51 + clang-tidy/misc/FoldInitTypeCheck.cpp | 140 + clang-tidy/misc/FoldInitTypeCheck.h | 44 + .../misc/ForwardDeclarationNamespaceCheck.cpp | 174 ++ .../misc/ForwardDeclarationNamespaceCheck.h | 59 + clang-tidy/misc/InaccurateEraseCheck.cpp | 73 + clang-tidy/misc/InaccurateEraseCheck.h | 38 + clang-tidy/misc/IncorrectRoundings.cpp | 71 + clang-tidy/misc/IncorrectRoundings.h | 39 + clang-tidy/misc/InefficientAlgorithmCheck.cpp | 163 ++ clang-tidy/misc/InefficientAlgorithmCheck.h | 36 + clang-tidy/misc/MacroParenthesesCheck.cpp | 260 ++ clang-tidy/misc/MacroParenthesesCheck.h | 43 + .../misc/MacroRepeatedSideEffectsCheck.cpp | 184 ++ .../misc/MacroRepeatedSideEffectsCheck.h | 31 + clang-tidy/misc/MiscTidyModule.cpp | 157 ++ clang-tidy/misc/MisplacedConstCheck.cpp | 63 + clang-tidy/misc/MisplacedConstCheck.h | 36 + .../misc/MisplacedWideningCastCheck.cpp | 229 ++ clang-tidy/misc/MisplacedWideningCastCheck.h | 46 + clang-tidy/misc/MoveConstantArgumentCheck.cpp | 101 + clang-tidy/misc/MoveConstantArgumentCheck.h | 31 + clang-tidy/misc/MoveConstructorInitCheck.cpp | 107 + clang-tidy/misc/MoveConstructorInitCheck.h | 44 + .../misc/MoveForwardingReferenceCheck.cpp | 133 + .../misc/MoveForwardingReferenceCheck.h | 49 + .../misc/MultipleStatementMacroCheck.cpp | 106 + clang-tidy/misc/MultipleStatementMacroCheck.h | 37 + clang-tidy/misc/NewDeleteOverloadsCheck.cpp | 213 ++ clang-tidy/misc/NewDeleteOverloadsCheck.h | 38 + .../misc/NoexceptMoveConstructorCheck.cpp | 73 + .../misc/NoexceptMoveConstructorCheck.h | 38 + clang-tidy/misc/NonCopyableObjects.cpp | 74 + clang-tidy/misc/NonCopyableObjects.h | 33 + clang-tidy/misc/RedundantExpressionCheck.cpp | 740 ++++++ clang-tidy/misc/RedundantExpressionCheck.h | 40 + clang-tidy/misc/SizeofContainerCheck.cpp | 49 + clang-tidy/misc/SizeofContainerCheck.h | 36 + clang-tidy/misc/SizeofExpressionCheck.cpp | 265 ++ clang-tidy/misc/SizeofExpressionCheck.h | 40 + clang-tidy/misc/StaticAssertCheck.cpp | 175 ++ clang-tidy/misc/StaticAssertCheck.h | 41 + clang-tidy/misc/StringCompareCheck.cpp | 82 + clang-tidy/misc/StringCompareCheck.h | 36 + clang-tidy/misc/StringConstructorCheck.cpp | 134 + clang-tidy/misc/StringConstructorCheck.h | 39 + .../misc/StringIntegerAssignmentCheck.cpp | 86 + .../misc/StringIntegerAssignmentCheck.h | 35 + .../StringLiteralWithEmbeddedNulCheck.cpp | 83 + .../misc/StringLiteralWithEmbeddedNulCheck.h | 35 + clang-tidy/misc/SuspiciousEnumUsageCheck.cpp | 216 ++ clang-tidy/misc/SuspiciousEnumUsageCheck.h | 39 + .../misc/SuspiciousMissingCommaCheck.cpp | 129 + clang-tidy/misc/SuspiciousMissingCommaCheck.h | 44 + clang-tidy/misc/SuspiciousSemicolonCheck.cpp | 77 + clang-tidy/misc/SuspiciousSemicolonCheck.h | 36 + .../misc/SuspiciousStringCompareCheck.cpp | 218 ++ .../misc/SuspiciousStringCompareCheck.h | 40 + clang-tidy/misc/SwappedArgumentsCheck.cpp | 102 + clang-tidy/misc/SwappedArgumentsCheck.h | 32 + .../ThrowByValueCatchByReferenceCheck.cpp | 159 ++ .../misc/ThrowByValueCatchByReferenceCheck.h | 51 + .../UnconventionalAssignOperatorCheck.cpp | 91 + .../misc/UnconventionalAssignOperatorCheck.h | 42 + clang-tidy/misc/UndelegatedConstructor.cpp | 84 + clang-tidy/misc/UndelegatedConstructor.h | 36 + .../misc/UniqueptrResetReleaseCheck.cpp | 136 + clang-tidy/misc/UniqueptrResetReleaseCheck.h | 43 + clang-tidy/misc/UnusedAliasDeclsCheck.cpp | 64 + clang-tidy/misc/UnusedAliasDeclsCheck.h | 37 + clang-tidy/misc/UnusedParametersCheck.cpp | 126 + clang-tidy/misc/UnusedParametersCheck.h | 38 + clang-tidy/misc/UnusedRAIICheck.cpp | 94 + clang-tidy/misc/UnusedRAIICheck.h | 35 + clang-tidy/misc/UnusedUsingDeclsCheck.cpp | 164 ++ clang-tidy/misc/UnusedUsingDeclsCheck.h | 58 + clang-tidy/misc/UseAfterMoveCheck.cpp | 425 +++ clang-tidy/misc/UseAfterMoveCheck.h | 36 + clang-tidy/misc/VirtualNearMissCheck.cpp | 274 ++ clang-tidy/misc/VirtualNearMissCheck.h | 65 + clang-tidy/modernize/AvoidBindCheck.cpp | 181 ++ clang-tidy/modernize/AvoidBindCheck.h | 36 + clang-tidy/modernize/CMakeLists.txt | 36 + .../modernize/DeprecatedHeadersCheck.cpp | 123 + clang-tidy/modernize/DeprecatedHeadersCheck.h | 47 + clang-tidy/modernize/LoopConvertCheck.cpp | 918 +++++++ clang-tidy/modernize/LoopConvertCheck.h | 78 + clang-tidy/modernize/LoopConvertUtils.cpp | 909 +++++++ clang-tidy/modernize/LoopConvertUtils.h | 467 ++++ clang-tidy/modernize/MakeSharedCheck.cpp | 31 + clang-tidy/modernize/MakeSharedCheck.h | 43 + clang-tidy/modernize/MakeSmartPtrCheck.cpp | 206 ++ clang-tidy/modernize/MakeSmartPtrCheck.h | 59 + clang-tidy/modernize/MakeUniqueCheck.cpp | 40 + clang-tidy/modernize/MakeUniqueCheck.h | 40 + clang-tidy/modernize/ModernizeTidyModule.cpp | 102 + clang-tidy/modernize/PassByValueCheck.cpp | 232 ++ clang-tidy/modernize/PassByValueCheck.h | 40 + .../modernize/RawStringLiteralCheck.cpp | 141 + clang-tidy/modernize/RawStringLiteralCheck.h | 44 + .../modernize/RedundantVoidArgCheck.cpp | 248 ++ clang-tidy/modernize/RedundantVoidArgCheck.h | 77 + clang-tidy/modernize/ReplaceAutoPtrCheck.cpp | 271 ++ clang-tidy/modernize/ReplaceAutoPtrCheck.h | 61 + clang-tidy/modernize/ShrinkToFitCheck.cpp | 90 + clang-tidy/modernize/ShrinkToFitCheck.h | 37 + clang-tidy/modernize/UseAutoCheck.cpp | 463 ++++ clang-tidy/modernize/UseAutoCheck.h | 39 + clang-tidy/modernize/UseBoolLiteralsCheck.cpp | 66 + clang-tidy/modernize/UseBoolLiteralsCheck.h | 35 + .../modernize/UseDefaultMemberInitCheck.cpp | 238 ++ .../modernize/UseDefaultMemberInitCheck.h | 45 + clang-tidy/modernize/UseEmplaceCheck.cpp | 143 + clang-tidy/modernize/UseEmplaceCheck.h | 44 + .../modernize/UseEqualsDefaultCheck.cpp | 299 +++ clang-tidy/modernize/UseEqualsDefaultCheck.h | 50 + clang-tidy/modernize/UseEqualsDeleteCheck.cpp | 73 + clang-tidy/modernize/UseEqualsDeleteCheck.h | 50 + clang-tidy/modernize/UseNullptrCheck.cpp | 495 ++++ clang-tidy/modernize/UseNullptrCheck.h | 35 + clang-tidy/modernize/UseOverrideCheck.cpp | 203 ++ clang-tidy/modernize/UseOverrideCheck.h | 32 + .../modernize/UseTransparentFunctorsCheck.cpp | 131 + .../modernize/UseTransparentFunctorsCheck.h | 37 + clang-tidy/modernize/UseUsingCheck.cpp | 94 + clang-tidy/modernize/UseUsingCheck.h | 35 + clang-tidy/mpi/BufferDerefCheck.cpp | 132 + clang-tidy/mpi/BufferDerefCheck.h | 51 + clang-tidy/mpi/CMakeLists.txt | 17 + clang-tidy/mpi/MPITidyModule.cpp | 39 + clang-tidy/mpi/TypeMismatchCheck.cpp | 335 +++ clang-tidy/mpi/TypeMismatchCheck.h | 51 + clang-tidy/performance/CMakeLists.txt | 20 + .../performance/FasterStringFindCheck.cpp | 112 + .../performance/FasterStringFindCheck.h | 43 + clang-tidy/performance/ForRangeCopyCheck.cpp | 95 + clang-tidy/performance/ForRangeCopyCheck.h | 49 + .../performance/ImplicitCastInLoopCheck.cpp | 97 + .../performance/ImplicitCastInLoopCheck.h | 38 + .../InefficientStringConcatenationCheck.cpp | 86 + .../InefficientStringConcatenationCheck.h | 41 + .../performance/PerformanceTidyModule.cpp | 56 + .../TypePromotionInMathFnCheck.cpp | 205 ++ .../performance/TypePromotionInMathFnCheck.h | 47 + .../UnnecessaryCopyInitialization.cpp | 149 ++ .../UnnecessaryCopyInitialization.h | 47 + .../UnnecessaryValueParamCheck.cpp | 190 ++ .../performance/UnnecessaryValueParamCheck.h | 45 + clang-tidy/plugin/CMakeLists.txt | 22 + clang-tidy/plugin/ClangTidyPlugin.cpp | 127 + .../readability/AvoidConstParamsInDecls.cpp | 122 + .../readability/AvoidConstParamsInDecls.h | 34 + .../BracesAroundStatementsCheck.cpp | 277 ++ .../readability/BracesAroundStatementsCheck.h | 69 + clang-tidy/readability/CMakeLists.txt | 38 + .../readability/ContainerSizeEmptyCheck.cpp | 166 ++ .../readability/ContainerSizeEmptyCheck.h | 40 + .../readability/DeleteNullPointerCheck.cpp | 76 + .../readability/DeleteNullPointerCheck.h | 35 + .../readability/DeletedDefaultCheck.cpp | 69 + clang-tidy/readability/DeletedDefaultCheck.h | 36 + .../readability/ElseAfterReturnCheck.cpp | 56 + clang-tidy/readability/ElseAfterReturnCheck.h | 34 + clang-tidy/readability/FunctionSizeCheck.cpp | 134 + clang-tidy/readability/FunctionSizeCheck.h | 48 + .../readability/IdentifierNamingCheck.cpp | 917 +++++++ .../readability/IdentifierNamingCheck.h | 113 + .../readability/ImplicitBoolCastCheck.cpp | 432 ++++ .../readability/ImplicitBoolCastCheck.h | 46 + ...onsistentDeclarationParameterNameCheck.cpp | 339 +++ ...nconsistentDeclarationParameterNameCheck.h | 45 + .../readability/MisplacedArrayIndexCheck.cpp | 57 + .../readability/MisplacedArrayIndexCheck.h | 36 + .../readability/NamedParameterCheck.cpp | 127 + clang-tidy/readability/NamedParameterCheck.h | 42 + .../readability/NamespaceCommentCheck.cpp | 147 ++ .../readability/NamespaceCommentCheck.h | 43 + .../readability/NonConstParameterCheck.cpp | 214 ++ .../readability/NonConstParameterCheck.h | 64 + .../readability/ReadabilityTidyModule.cpp | 104 + .../readability/RedundantControlFlowCheck.cpp | 98 + .../readability/RedundantControlFlowCheck.h | 51 + .../readability/RedundantDeclarationCheck.cpp | 73 + .../readability/RedundantDeclarationCheck.h | 35 + .../RedundantFunctionPtrDereferenceCheck.cpp | 37 + .../RedundantFunctionPtrDereferenceCheck.h | 35 + .../readability/RedundantMemberInitCheck.cpp | 68 + .../readability/RedundantMemberInitCheck.h | 36 + .../readability/RedundantSmartptrGetCheck.cpp | 138 + .../readability/RedundantSmartptrGetCheck.h | 40 + .../readability/RedundantStringCStrCheck.cpp | 198 ++ .../readability/RedundantStringCStrCheck.h | 32 + .../readability/RedundantStringInitCheck.cpp | 70 + .../readability/RedundantStringInitCheck.h | 32 + .../readability/SimplifyBooleanExprCheck.cpp | 683 +++++ .../readability/SimplifyBooleanExprCheck.h | 102 + ...ticDefinitionInAnonymousNamespaceCheck.cpp | 66 + ...taticDefinitionInAnonymousNamespaceCheck.h | 36 + .../UniqueptrDeleteReleaseCheck.cpp | 69 + .../readability/UniqueptrDeleteReleaseCheck.h | 36 + clang-tidy/rename_check.py | 108 + clang-tidy/tool/CMakeLists.txt | 33 + clang-tidy/tool/ClangTidyMain.cpp | 483 ++++ clang-tidy/tool/clang-tidy-diff.py | 121 + clang-tidy/tool/run-clang-tidy.py | 199 ++ clang-tidy/utils/ASTUtils.cpp | 28 + clang-tidy/utils/ASTUtils.h | 25 + clang-tidy/utils/CMakeLists.txt | 24 + clang-tidy/utils/DeclRefExprUtils.cpp | 171 ++ clang-tidy/utils/DeclRefExprUtils.h | 66 + clang-tidy/utils/ExprSequence.cpp | 182 ++ clang-tidy/utils/ExprSequence.h | 124 + clang-tidy/utils/FixItHintUtils.cpp | 36 + clang-tidy/utils/FixItHintUtils.h | 32 + .../utils/HeaderFileExtensionsUtils.cpp | 71 + clang-tidy/utils/HeaderFileExtensionsUtils.h | 52 + clang-tidy/utils/HeaderGuard.cpp | 292 +++ clang-tidy/utils/HeaderGuard.h | 64 + clang-tidy/utils/IncludeInserter.cpp | 85 + clang-tidy/utils/IncludeInserter.h | 84 + clang-tidy/utils/IncludeSorter.cpp | 290 +++ clang-tidy/utils/IncludeSorter.h | 86 + clang-tidy/utils/LexerUtils.cpp | 41 + clang-tidy/utils/LexerUtils.h | 31 + clang-tidy/utils/Matchers.h | 51 + clang-tidy/utils/NamespaceAliaser.cpp | 97 + clang-tidy/utils/NamespaceAliaser.h | 52 + clang-tidy/utils/OptionsUtils.cpp | 38 + clang-tidy/utils/OptionsUtils.h | 32 + clang-tidy/utils/TypeTraits.cpp | 149 ++ clang-tidy/utils/TypeTraits.h | 43 + clang-tidy/utils/UsingInserter.cpp | 89 + clang-tidy/utils/UsingInserter.h | 50 + docs/CMakeLists.txt | 102 + docs/Doxyfile | 1808 +++++++++++++ docs/ModularizeUsage.rst | 98 + docs/README.txt | 11 + docs/ReleaseNotes.rst | 180 ++ docs/clang-modernize.rst | 4 + docs/clang-rename.rst | 165 ++ docs/clang-tidy.rst | 6 + .../clang-tidy/checks/boost-use-to-string.rst | 22 + docs/clang-tidy/checks/cert-dcl03-c.rst | 9 + docs/clang-tidy/checks/cert-dcl50-cpp.rst | 11 + docs/clang-tidy/checks/cert-dcl54-cpp.rst | 10 + docs/clang-tidy/checks/cert-dcl59-cpp.rst | 9 + docs/clang-tidy/checks/cert-env33-c.rst | 13 + docs/clang-tidy/checks/cert-err09-cpp.rst | 10 + docs/clang-tidy/checks/cert-err34-c.rst | 28 + docs/clang-tidy/checks/cert-err52-cpp.rst | 10 + docs/clang-tidy/checks/cert-err58-cpp.rst | 11 + docs/clang-tidy/checks/cert-err60-cpp.rst | 11 + docs/clang-tidy/checks/cert-err61-cpp.rst | 10 + docs/clang-tidy/checks/cert-fio38-c.rst | 10 + docs/clang-tidy/checks/cert-flp30-c.rst | 11 + docs/clang-tidy/checks/cert-msc30-c.rst | 9 + docs/clang-tidy/checks/cert-msc50-cpp.rst | 11 + docs/clang-tidy/checks/cert-oop11-cpp.rst | 10 + ...pcoreguidelines-interfaces-global-init.rst | 14 + .../checks/cppcoreguidelines-no-malloc.rst | 27 + ...ines-pro-bounds-array-to-pointer-decay.rst | 12 + ...elines-pro-bounds-constant-array-index.rst | 25 + ...idelines-pro-bounds-pointer-arithmetic.rst | 14 + .../cppcoreguidelines-pro-type-const-cast.rst | 12 + ...cppcoreguidelines-pro-type-cstyle-cast.rst | 18 + ...cppcoreguidelines-pro-type-member-init.rst | 38 + ...reguidelines-pro-type-reinterpret-cast.rst | 13 + ...idelines-pro-type-static-cast-downcast.rst | 15 + ...ppcoreguidelines-pro-type-union-access.rst | 16 + .../cppcoreguidelines-pro-type-vararg.rst | 17 + .../checks/cppcoreguidelines-slicing.rst | 25 + ...oreguidelines-special-member-functions.rst | 21 + .../google-build-explicit-make-pair.rst | 11 + .../checks/google-build-namespaces.rst | 23 + .../checks/google-build-using-namespace.rst | 19 + .../checks/google-default-arguments.rst | 8 + .../checks/google-explicit-constructor.rst | 56 + .../checks/google-global-names-in-headers.rst | 21 + ...e-readability-braces-around-statements.rst | 10 + .../checks/google-readability-casting.rst | 14 + .../google-readability-function-size.rst | 10 + .../google-readability-namespace-comments.rst | 9 + ...gle-readability-redundant-smartptr-get.rst | 10 + .../checks/google-readability-todo.rst | 11 + docs/clang-tidy/checks/google-runtime-int.rst | 27 + ...oogle-runtime-member-string-references.rst | 25 + .../checks/google-runtime-memset.rst | 10 + .../checks/google-runtime-operator.rst | 10 + .../checks/google-runtime-references.rst | 17 + docs/clang-tidy/checks/list.rst | 154 ++ docs/clang-tidy/checks/llvm-header-guard.rst | 17 + docs/clang-tidy/checks/llvm-include-order.rst | 9 + .../checks/llvm-namespace-comment.rst | 28 + docs/clang-tidy/checks/llvm-twine-local.rst | 8 + .../checks/misc-argument-comment.rst | 28 + .../checks/misc-assert-side-effect.rst | 23 + .../misc-bool-pointer-implicit-conversion.rst | 16 + .../checks/misc-dangling-handle.rst | 38 + .../checks/misc-definitions-in-headers.rst | 91 + .../clang-tidy/checks/misc-fold-init-type.rst | 27 + .../misc-forward-declaration-namespace.rst | 20 + .../checks/misc-inaccurate-erase.rst | 13 + .../checks/misc-incorrect-roundings.rst | 16 + .../checks/misc-inefficient-algorithm.rst | 11 + .../checks/misc-macro-parentheses.rst | 19 + .../misc-macro-repeated-side-effects.rst | 7 + .../checks/misc-misplaced-const.rst | 22 + .../checks/misc-misplaced-widening-cast.rst | 65 + .../clang-tidy/checks/misc-move-const-arg.rst | 29 + .../checks/misc-move-constructor-init.rst | 18 + .../checks/misc-move-forwarding-reference.rst | 60 + .../checks/misc-multiple-statement-macro.rst | 16 + .../checks/misc-new-delete-overloads.rst | 19 + .../checks/misc-noexcept-move-constructor.rst | 13 + .../checks/misc-non-copyable-objects.rst | 13 + .../checks/misc-redundant-expression.rst | 25 + .../checks/misc-sizeof-container.rst | 26 + .../checks/misc-sizeof-expression.rst | 154 ++ docs/clang-tidy/checks/misc-static-assert.rst | 12 + .../clang-tidy/checks/misc-string-compare.rst | 54 + .../checks/misc-string-constructor.rst | 44 + .../checks/misc-string-integer-assignment.rst | 37 + .../misc-string-literal-with-embedded-nul.rst | 36 + .../checks/misc-suspicious-enum-usage.rst | 78 + .../checks/misc-suspicious-missing-comma.rst | 59 + .../checks/misc-suspicious-semicolon.rst | 72 + .../checks/misc-suspicious-string-compare.rst | 64 + .../checks/misc-swapped-arguments.rst | 7 + ...misc-throw-by-value-catch-by-reference.rst | 34 + .../misc-unconventional-assign-operator.rst | 13 + .../checks/misc-undelegated-constructor.rst | 11 + .../checks/misc-uniqueptr-reset-release.rst | 16 + .../checks/misc-unused-alias-decls.rst | 7 + .../checks/misc-unused-parameters.rst | 7 + docs/clang-tidy/checks/misc-unused-raii.rst | 30 + .../checks/misc-unused-using-decls.rst | 13 + .../clang-tidy/checks/misc-use-after-move.rst | 203 ++ .../checks/misc-virtual-near-miss.rst | 20 + .../checks/modernize-avoid-bind.rst | 37 + .../checks/modernize-deprecated-headers.rst | 49 + .../checks/modernize-loop-convert.rst | 255 ++ .../checks/modernize-make-shared.rst | 27 + .../checks/modernize-make-unique.rst | 27 + .../checks/modernize-pass-by-value.rst | 166 ++ .../checks/modernize-raw-string-literal.rst | 46 + .../checks/modernize-redundant-void-arg.rst | 18 + .../checks/modernize-replace-auto-ptr.rst | 79 + .../checks/modernize-shrink-to-fit.rst | 12 + docs/clang-tidy/checks/modernize-use-auto.rst | 196 ++ .../checks/modernize-use-bool-literals.rst | 20 + .../modernize-use-default-member-init.rst | 49 + .../checks/modernize-use-default.rst | 11 + .../checks/modernize-use-emplace.rst | 101 + .../checks/modernize-use-equals-default.rst | 28 + .../checks/modernize-use-equals-delete.rst | 25 + .../checks/modernize-use-nullptr.rst | 67 + .../checks/modernize-use-override.rst | 7 + .../modernize-use-transparent-functors.rst | 39 + .../clang-tidy/checks/modernize-use-using.rst | 26 + docs/clang-tidy/checks/mpi-buffer-deref.rst | 26 + docs/clang-tidy/checks/mpi-type-mismatch.rst | 21 + .../checks/performance-faster-string-find.rst | 28 + .../checks/performance-for-range-copy.rst | 27 + .../performance-implicit-cast-in-loop.rst | 21 + ...mance-inefficient-string-concatenation.rst | 59 + .../performance-type-promotion-in-math-fn.rst | 11 + ...rmance-unnecessary-copy-initialization.rst | 37 + .../performance-unnecessary-value-param.rst | 63 + ...eadability-avoid-const-params-in-decls.rst | 17 + .../readability-braces-around-statements.rst | 38 + .../readability-container-size-empty.rst | 26 + .../readability-delete-null-pointer.rst | 13 + .../checks/readability-deleted-default.rst | 22 + .../checks/readability-else-after-return.rst | 64 + .../checks/readability-function-size.rst | 27 + .../checks/readability-identifier-naming.rst | 18 + .../checks/readability-implicit-bool-cast.rst | 130 + ...nconsistent-declaration-parameter-name.rst | 44 + .../readability-misplaced-array-index.rst | 27 + .../checks/readability-named-parameter.rst | 16 + .../readability-non-const-parameter.rst | 46 + .../readability-redundant-control-flow.rst | 50 + .../readability-redundant-declaration.rst | 29 + ...ity-redundant-function-ptr-dereference.rst | 24 + .../readability-redundant-member-init.rst | 20 + .../readability-redundant-smartptr-get.rst | 18 + .../readability-redundant-string-cstr.rst | 7 + .../readability-redundant-string-init.rst | 14 + .../readability-simplify-boolean-expr.rst | 86 + ...atic-definition-in-anonymous-namespace.rst | 18 + .../readability-uniqueptr-delete-release.rst | 7 + docs/clang-tidy/index.rst | 640 +++++ docs/clang-tidy/tools/dump_check_docs.py | 79 + docs/conf.py | 243 ++ docs/cpp11-migrate.rst | 4 + docs/doxygen-mainpage.dox | 9 + docs/doxygen.cfg.in | 2300 +++++++++++++++++ docs/include-fixer.rst | 155 ++ docs/index.rst | 33 + docs/make.bat | 190 ++ docs/modularize.rst | 265 ++ docs/pp-trace.rst | 825 ++++++ include-fixer/CMakeLists.txt | 27 + include-fixer/InMemorySymbolIndex.cpp | 32 + include-fixer/InMemorySymbolIndex.h | 37 + include-fixer/IncludeFixer.cpp | 445 ++++ include-fixer/IncludeFixer.h | 158 ++ include-fixer/IncludeFixerContext.cpp | 116 + include-fixer/IncludeFixerContext.h | 95 + include-fixer/SymbolIndex.h | 38 + include-fixer/SymbolIndexManager.cpp | 155 ++ include-fixer/SymbolIndexManager.h | 66 + include-fixer/YamlSymbolIndex.cpp | 60 + include-fixer/YamlSymbolIndex.h | 46 + include-fixer/find-all-symbols/CMakeLists.txt | 24 + .../find-all-symbols/FindAllMacros.cpp | 37 + .../find-all-symbols/FindAllMacros.h | 47 + .../find-all-symbols/FindAllSymbols.cpp | 236 ++ .../find-all-symbols/FindAllSymbols.h | 57 + .../find-all-symbols/FindAllSymbolsAction.cpp | 37 + .../find-all-symbols/FindAllSymbolsAction.h | 63 + .../find-all-symbols/HeaderMapCollector.cpp | 33 + .../find-all-symbols/HeaderMapCollector.h | 57 + include-fixer/find-all-symbols/PathConfig.cpp | 42 + include-fixer/find-all-symbols/PathConfig.h | 37 + .../find-all-symbols/PragmaCommentHandler.cpp | 37 + .../find-all-symbols/PragmaCommentHandler.h | 41 + .../find-all-symbols/STLPostfixHeaderMap.cpp | 650 +++++ .../find-all-symbols/STLPostfixHeaderMap.h | 23 + include-fixer/find-all-symbols/SymbolInfo.cpp | 119 + include-fixer/find-all-symbols/SymbolInfo.h | 129 + .../find-all-symbols/SymbolReporter.h | 30 + .../find-all-symbols/tool/CMakeLists.txt | 22 + .../tool/FindAllSymbolsMain.cpp | 162 ++ .../tool/run-find-all-symbols.py | 124 + include-fixer/plugin/CMakeLists.txt | 13 + include-fixer/plugin/IncludeFixerPlugin.cpp | 100 + include-fixer/tool/CMakeLists.txt | 26 + include-fixer/tool/ClangIncludeFixer.cpp | 443 ++++ .../tool/clang-include-fixer-test.el | 42 + include-fixer/tool/clang-include-fixer.el | 419 +++ include-fixer/tool/clang-include-fixer.py | 203 ++ modularize/CMakeLists.txt | 25 + modularize/CoverageChecker.cpp | 420 +++ modularize/CoverageChecker.h | 165 ++ modularize/Modularize.cpp | 998 +++++++ modularize/Modularize.h | 54 + modularize/ModularizeUtilities.cpp | 544 ++++ modularize/ModularizeUtilities.h | 225 ++ modularize/ModuleAssistant.cpp | 316 +++ modularize/PreprocessorTracker.cpp | 1393 ++++++++++ modularize/PreprocessorTracker.h | 87 + pp-trace/CMakeLists.txt | 16 + pp-trace/PPCallbacksTracker.cpp | 653 +++++ pp-trace/PPCallbacksTracker.h | 246 ++ pp-trace/PPTrace.cpp | 233 ++ test/.clang-format | 2 + test/CMakeLists.txt | 66 + test/Unit/lit.cfg | 54 + test/Unit/lit.site.cfg.in | 9 + test/change-namespace/lambda-function.cpp | 37 + test/change-namespace/macro.cpp | 29 + test/change-namespace/simple-move.cpp | 10 + .../Inputs/basic/basic.h | 32 + .../Inputs/basic/file1.yaml | 22 + .../Inputs/basic/file2.yaml | 10 + .../Inputs/conflict/common.h | 17 + .../Inputs/conflict/expected.txt | 11 + .../Inputs/conflict/file1.yaml | 18 + .../Inputs/conflict/file2.yaml | 18 + .../Inputs/conflict/file3.yaml | 10 + .../Inputs/crlf/crlf.cpp | 6 + .../Inputs/crlf/crlf.cpp.expected | 6 + .../Inputs/crlf/file1.yaml | 10 + .../Inputs/format/no.cpp | 6 + .../Inputs/format/no.yaml | 10 + .../Inputs/format/yes.cpp | 22 + .../Inputs/format/yes.yaml | 24 + test/clang-apply-replacements/basic.cpp | 17 + test/clang-apply-replacements/conflict.cpp | 17 + test/clang-apply-replacements/crlf.cpp | 5 + test/clang-apply-replacements/format.cpp | 15 + test/clang-move/Inputs/database_template.json | 7 + test/clang-move/Inputs/enum.h | 9 + test/clang-move/Inputs/function_test.cpp | 5 + test/clang-move/Inputs/function_test.h | 14 + test/clang-move/Inputs/helper_decls_test.cpp | 78 + test/clang-move/Inputs/helper_decls_test.h | 35 + .../clang-move/Inputs/multiple_class_test.cpp | 52 + test/clang-move/Inputs/multiple_class_test.h | 39 + .../clang-move/Inputs/template_class_test.cpp | 13 + test/clang-move/Inputs/template_class_test.h | 30 + test/clang-move/Inputs/test.cpp | 11 + test/clang-move/Inputs/test.h | 10 + test/clang-move/Inputs/type_alias.h | 11 + test/clang-move/move-class.cpp | 43 + test/clang-move/move-enum-decl.cpp | 44 + test/clang-move/move-function.cpp | 67 + test/clang-move/move-multiple-classes.cpp | 104 + test/clang-move/move-template-class.cpp | 86 + test/clang-move/move-type-alias.cpp | 52 + test/clang-move/move-used-helper-decls.cpp | 388 +++ test/clang-query/Inputs/foo.script | 2 + test/clang-query/errors.c | 10 + test/clang-query/function-decl.c | 4 + test/clang-rename/ClassAsTemplateArgument.cpp | 21 + test/clang-rename/ClassFindByName.cpp | 10 + test/clang-rename/ClassReplacements.cpp | 11 + test/clang-rename/ClassSimpleRenaming.cpp | 14 + test/clang-rename/ClassTestMulti.cpp | 11 + test/clang-rename/ClassTestMultiByName.cpp | 8 + test/clang-rename/ComplexFunctionOverride.cpp | 47 + test/clang-rename/ComplicatedClassType.cpp | 63 + test/clang-rename/Ctor.cpp | 14 + test/clang-rename/CtorInitializer.cpp | 17 + test/clang-rename/DeclRefExpr.cpp | 24 + test/clang-rename/Field.cpp | 15 + test/clang-rename/FunctionMacro.cpp | 20 + test/clang-rename/FunctionOverride.cpp | 13 + .../FunctionWithClassFindByName.cpp | 12 + test/clang-rename/IncludeHeaderWithSymbol.cpp | 10 + test/clang-rename/Inputs/HeaderWithSymbol.h | 1 + test/clang-rename/Inputs/OffsetToNewName.yaml | 6 + .../Inputs/QualifiedNameToNewName.yaml | 6 + test/clang-rename/InvalidNewName.cpp | 2 + test/clang-rename/InvalidOffset.cpp | 9 + test/clang-rename/MemberExprMacro.cpp | 22 + test/clang-rename/Namespace.cpp | 13 + test/clang-rename/NoNewName.cpp | 4 + .../TemplateClassInstantiation.cpp | 42 + test/clang-rename/TemplateTypename.cpp | 24 + test/clang-rename/TemplatedClassFunction.cpp | 22 + test/clang-rename/UserDefinedConversion.cpp | 26 + test/clang-rename/Variable.cpp | 33 + test/clang-rename/VariableMacro.cpp | 21 + test/clang-rename/YAMLInput.cpp | 10 + .../AggregatePartialInitialization.cpp | 14 + .../CStructAmbiguousName.cpp | 18 + .../CStructFieldsOrder.cpp | 16 + .../ClassDifferentFieldsAccesses.cpp | 16 + .../ClassMixedInitialization.cpp | 24 + test/clang-reorder-fields/ClassSimpleCtor.cpp | 24 + test/clang-tidy/Inputs/Headers/a.h | 0 test/clang-tidy/Inputs/Headers/b.h | 0 test/clang-tidy/Inputs/Headers/clang-c/c.h | 0 test/clang-tidy/Inputs/Headers/clang/b.h | 0 test/clang-tidy/Inputs/Headers/cross-file-a.h | 0 test/clang-tidy/Inputs/Headers/cross-file-b.h | 0 test/clang-tidy/Inputs/Headers/cross-file-c.h | 41 + test/clang-tidy/Inputs/Headers/gtest/foo.h | 0 test/clang-tidy/Inputs/Headers/i.h | 0 test/clang-tidy/Inputs/Headers/j.h | 0 test/clang-tidy/Inputs/Headers/llvm-c/d.h | 0 test/clang-tidy/Inputs/Headers/llvm/a.h | 0 test/clang-tidy/Inputs/Headers/s.h | 0 .../Inputs/compilation-database/template.json | 32 + .../Inputs/config-files/.clang-tidy | 2 + .../Inputs/config-files/1/.clang-tidy | 2 + .../Inputs/explain-config/.clang-tidy | 1 + test/clang-tidy/Inputs/file-filter/header1.h | 1 + test/clang-tidy/Inputs/file-filter/header2.h | 1 + .../Inputs/file-filter/system/system-header.h | 1 + test/clang-tidy/Inputs/google-namespaces.h | 7 + test/clang-tidy/Inputs/line-filter/header1.h | 3 + test/clang-tidy/Inputs/line-filter/header2.h | 3 + test/clang-tidy/Inputs/line-filter/header3.h | 1 + .../modernize-deprecated-headers/assert.h | 0 .../modernize-deprecated-headers/complex.h | 0 .../modernize-deprecated-headers/ctype.h | 0 .../modernize-deprecated-headers/errno.h | 0 .../modernize-deprecated-headers/fenv.h | 0 .../modernize-deprecated-headers/float.h | 0 .../modernize-deprecated-headers/inttypes.h | 0 .../modernize-deprecated-headers/iso646.h | 0 .../modernize-deprecated-headers/limits.h | 0 .../modernize-deprecated-headers/locale.h | 0 .../modernize-deprecated-headers/math.h | 0 .../modernize-deprecated-headers/setjmp.h | 0 .../modernize-deprecated-headers/signal.h | 0 .../modernize-deprecated-headers/stdalign.h | 0 .../modernize-deprecated-headers/stdarg.h | 0 .../modernize-deprecated-headers/stdbool.h | 0 .../modernize-deprecated-headers/stddef.h | 0 .../modernize-deprecated-headers/stdint.h | 0 .../modernize-deprecated-headers/stdio.h | 0 .../modernize-deprecated-headers/stdlib.h | 0 .../modernize-deprecated-headers/string.h | 0 .../modernize-deprecated-headers/tgmath.h | 0 .../modernize-deprecated-headers/time.h | 0 .../modernize-deprecated-headers/uchar.h | 0 .../modernize-deprecated-headers/wchar.h | 0 .../modernize-deprecated-headers/wctype.h | 0 .../modernize-loop-convert/structures.h | 206 ++ .../modernize-pass-by-value/header-with-fix.h | 8 + .../Inputs/modernize-pass-by-value/header.h | 10 + .../modernize-replace-auto-ptr/memory.h | 45 + .../Inputs/modernize-use-auto/containers.h | 253 ++ .../Inputs/mpi-type-mismatch/mpimock.h | 64 + .../Inputs/nolint/trigger_warning.h | 5 + test/clang-tidy/Inputs/overlapping/o.h | 9 + .../system/system-header.h | 17 + .../user-header.h | 17 + test/clang-tidy/Inputs/unused-using-decls.h | 11 + test/clang-tidy/basic.cpp | 6 + test/clang-tidy/boost-use-to-string.cpp | 169 ++ test/clang-tidy/cert-env33-c.c | 20 + test/clang-tidy/cert-err34-c.c | 103 + test/clang-tidy/cert-err34-c.cpp | 43 + test/clang-tidy/cert-flp30-c.c | 19 + test/clang-tidy/cert-limited-randomness.c | 13 + test/clang-tidy/cert-limited-randomness.cpp | 28 + test/clang-tidy/cert-oop11-cpp.cpp | 21 + test/clang-tidy/cert-setlongjmp.cpp | 26 + .../cert-static-object-exception.cpp | 194 ++ test/clang-tidy/cert-throw-exception-type.cpp | 127 + .../clang-tidy/cert-variadic-function-def.cpp | 24 + test/clang-tidy/check_clang_tidy.py | 135 + test/clang-tidy/clang-tidy-diff.cpp | 18 + .../clang-tidy-run-with-database.cpp | 23 + test/clang-tidy/clean-up-code.cpp | 12 + test/clang-tidy/config-files.cpp | 12 + ...pcoreguidelines-interfaces-global-init.cpp | 84 + .../cppcoreguidelines-no-malloc.cpp | 42 + ...ines-pro-bounds-array-to-pointer-decay.cpp | 47 + ...-pro-bounds-constant-array-index-c++03.cpp | 11 + ...-bounds-constant-array-index-gslheader.cpp | 79 + ...elines-pro-bounds-constant-array-index.cpp | 87 + ...idelines-pro-bounds-pointer-arithmetic.cpp | 89 + .../cppcoreguidelines-pro-type-const-cast.cpp | 6 + ...cppcoreguidelines-pro-type-cstyle-cast.cpp | 141 + ...eguidelines-pro-type-member-init-cxx98.cpp | 116 + ...uidelines-pro-type-member-init-delayed.cpp | 32 + ...cppcoreguidelines-pro-type-member-init.cpp | 475 ++++ ...reguidelines-pro-type-reinterpret-cast.cpp | 6 + ...idelines-pro-type-static-cast-downcast.cpp | 118 + ...ppcoreguidelines-pro-type-union-access.cpp | 41 + .../cppcoreguidelines-pro-type-vararg.cpp | 51 + test/clang-tidy/cppcoreguidelines-slicing.cpp | 100 + ...elines-special-member-functions-cxx-03.cpp | 26 + ...oreguidelines-special-member-functions.cpp | 67 + test/clang-tidy/custom-diagnostics.cpp | 27 + test/clang-tidy/deduplication.cpp | 10 + test/clang-tidy/diagnostic.cpp | 19 + test/clang-tidy/explain-checks.cpp | 12 + test/clang-tidy/extra-args.cpp | 7 + test/clang-tidy/file-filter.cpp | 45 + test/clang-tidy/fix-errors.cpp | 15 + test/clang-tidy/fix.cpp | 18 + .../google-build-explicit-make-pair.cpp | 51 + test/clang-tidy/google-default-arguments.cpp | 29 + .../google-explicit-constructor.cpp | 178 ++ test/clang-tidy/google-module.cpp | 10 + test/clang-tidy/google-namespaces.cpp | 8 + .../google-overloaded-unary-and.cpp | 25 + test/clang-tidy/google-readability-casting.c | 22 + .../clang-tidy/google-readability-casting.cpp | 150 ++ .../google-readability-namespace-comments.cpp | 40 + test/clang-tidy/google-readability-todo.cpp | 26 + test/clang-tidy/google-runtime-int-std.cpp | 57 + test/clang-tidy/google-runtime-int.c | 27 + test/clang-tidy/google-runtime-int.cpp | 74 + ...oogle-runtime-member-string-references.cpp | 49 + .../google-runtime-memset-zero-length.cpp | 62 + test/clang-tidy/google-runtime-references.cpp | 152 ++ test/clang-tidy/line-filter.cpp | 27 + test/clang-tidy/list-checks.cpp | 4 + test/clang-tidy/llvm-include-order.cpp | 42 + test/clang-tidy/llvm-twine-local.cpp | 32 + test/clang-tidy/macros.cpp | 7 + .../misc-argument-comment-strict.cpp | 19 + test/clang-tidy/misc-argument-comment.cpp | 53 + test/clang-tidy/misc-assert-side-effect.cpp | 114 + .../misc-bool-pointer-implicit-conversion.cpp | 82 + test/clang-tidy/misc-dangling-handle.cpp | 191 ++ .../misc-definitions-in-headers.hpp | 165 ++ test/clang-tidy/misc-fold-init-type.cpp | 158 ++ .../misc-forward-declaration-namespace.cpp | 163 ++ test/clang-tidy/misc-inaccurate-erase.cpp | 76 + test/clang-tidy/misc-incorrect-roundings.cpp | 86 + .../clang-tidy/misc-inefficient-algorithm.cpp | 162 ++ .../misc-macro-parentheses-cmdline.cpp | 10 + test/clang-tidy/misc-macro-parentheses.cpp | 49 + .../misc-macro-repeated-side-effects.c | 106 + test/clang-tidy/misc-misplaced-const.c | 45 + test/clang-tidy/misc-misplaced-const.cpp | 44 + ...-misplaced-widening-cast-explicit-only.cpp | 58 + .../misc-misplaced-widening-cast.cpp | 101 + test/clang-tidy/misc-move-const-arg.cpp | 160 ++ .../clang-tidy/misc-move-constructor-init.cpp | 144 ++ .../misc-move-forwarding-reference.cpp | 125 + .../misc-multiple-statement-macro.cpp | 85 + ...isc-new-delete-overloads-sized-dealloc.cpp | 20 + test/clang-tidy/misc-new-delete-overloads.cpp | 81 + .../misc-noexcept-move-constructor.cpp | 44 + test/clang-tidy/misc-non-copyable-objects.c | 43 + test/clang-tidy/misc-non-copyable-objects.cpp | 26 + test/clang-tidy/misc-redundant-expression.cpp | 485 ++++ test/clang-tidy/misc-sizeof-container.cpp | 103 + test/clang-tidy/misc-sizeof-expression.cpp | 186 ++ test/clang-tidy/misc-static-assert.c | 27 + test/clang-tidy/misc-static-assert.cpp | 140 + test/clang-tidy/misc-string-compare.cpp | 119 + test/clang-tidy/misc-string-constructor.cpp | 56 + .../misc-string-integer-assignment.cpp | 53 + .../misc-string-literal-with-embedded-nul.cpp | 85 + .../misc-suspicious-enum-usage-strict.cpp | 98 + .../clang-tidy/misc-suspicious-enum-usage.cpp | 90 + .../misc-suspicious-missing-comma.cpp | 82 + .../misc-suspicious-semicolon-fail.cpp | 12 + test/clang-tidy/misc-suspicious-semicolon.cpp | 117 + .../misc-suspicious-string-compare.c | 79 + .../misc-suspicious-string-compare.cpp | 337 +++ test/clang-tidy/misc-swapped-arguments.cpp | 53 + ...misc-throw-by-value-catch-by-reference.cpp | 156 ++ .../misc-unconventional-assign-operator.cpp | 89 + .../misc-undelegated-constructor-cxx98.cpp | 23 + .../misc-undelegated-constructor.cpp | 54 + .../misc-uniqueptr-reset-release.cpp | 69 + test/clang-tidy/misc-unused-alias-decls.cpp | 12 + test/clang-tidy/misc-unused-parameters.c | 17 + test/clang-tidy/misc-unused-parameters.cpp | 158 ++ test/clang-tidy/misc-unused-raii.cpp | 68 + test/clang-tidy/misc-unused-using-decls.cpp | 203 ++ test/clang-tidy/misc-use-after-move.cpp | 1124 ++++++++ test/clang-tidy/misc-virtual-near-miss.cpp | 133 + test/clang-tidy/modernize-avoid-bind.cpp | 79 + .../modernize-deprecated-headers-cxx03.cpp | 148 ++ .../modernize-deprecated-headers-cxx11.cpp | 163 ++ .../modernize-loop-convert-assert-failure.cpp | 18 + .../modernize-loop-convert-basic.cpp | 793 ++++++ .../modernize-loop-convert-camelback.cpp | 33 + .../modernize-loop-convert-const.cpp | 360 +++ .../modernize-loop-convert-extra.cpp | 1074 ++++++++ .../modernize-loop-convert-lowercase.cpp | 41 + .../modernize-loop-convert-negative.cpp | 485 ++++ .../modernize-loop-convert-uppercase.cpp | 41 + test/clang-tidy/modernize-loop-convert.c | 12 + test/clang-tidy/modernize-make-shared.cpp | 306 +++ test/clang-tidy/modernize-make-unique.cpp | 309 +++ .../modernize-pass-by-value-header.cpp | 8 + .../modernize-pass-by-value-macro-header.cpp | 18 + .../modernize-pass-by-value-multi-fixes.cpp | 12 + test/clang-tidy/modernize-pass-by-value.cpp | 215 ++ ...modernize-raw-string-literal-delimiter.cpp | 9 + .../modernize-raw-string-literal.cpp | 129 + .../modernize-redundant-void-arg-delayed.cpp | 28 + .../clang-tidy/modernize-redundant-void-arg.c | 58 + .../modernize-redundant-void-arg.cpp | 447 ++++ .../clang-tidy/modernize-replace-auto-ptr.cpp | 304 +++ test/clang-tidy/modernize-shrink-to-fit.cpp | 74 + .../modernize-use-auto-cast-remove-stars.cpp | 233 ++ test/clang-tidy/modernize-use-auto-cast.cpp | 232 ++ .../modernize-use-auto-iterator.cpp | 320 +++ .../modernize-use-auto-new-remove-stars.cpp | 106 + test/clang-tidy/modernize-use-auto-new.cpp | 108 + .../modernize-use-bool-literals.cpp | 148 ++ ...ize-use-default-member-init-assignment.cpp | 184 ++ .../modernize-use-default-member-init.cpp | 382 +++ test/clang-tidy/modernize-use-emplace.cpp | 447 ++++ .../modernize-use-equals-default-copy.cpp | 497 ++++ .../modernize-use-equals-default-delayed.cpp | 8 + .../modernize-use-equals-default.cpp | 209 ++ .../modernize-use-equals-delete.cpp | 160 ++ .../modernize-use-nullptr-basic.cpp | 361 +++ test/clang-tidy/modernize-use-nullptr.c | 10 + test/clang-tidy/modernize-use-nullptr.cpp | 230 ++ .../modernize-use-override-cxx98.cpp | 19 + test/clang-tidy/modernize-use-override-ms.cpp | 25 + test/clang-tidy/modernize-use-override.cpp | 290 +++ .../modernize-use-transparent-functors.cpp | 107 + test/clang-tidy/modernize-use-using.cpp | 87 + test/clang-tidy/mpi-buffer-deref.cpp | 50 + test/clang-tidy/mpi-type-mismatch.cpp | 255 ++ test/clang-tidy/nolint.cpp | 39 + test/clang-tidy/overlapping.cpp | 10 + .../performance-faster-string-find.cpp | 110 + ...for-range-copy-warn-on-all-auto-copies.cpp | 39 + .../clang-tidy/performance-for-range-copy.cpp | 248 ++ .../performance-implicit-cast-in-loop.cpp | 161 ++ ...mance-inefficient-string-concatenation.cpp | 44 + .../performance-type-promotion-in-math-fn.cpp | 316 +++ ...rmance-unnecessary-copy-initialization.cpp | 389 +++ ...rmance-unnecessary-value-param-delayed.cpp | 181 ++ ...nnecessary-value-param-incomplete-type.cpp | 9 + .../performance-unnecessary-value-param.cpp | 322 +++ ...eadability-avoid-const-params-in-decls.cpp | 133 + ...races-around-statements-assert-failure.cpp | 12 + ...ity-braces-around-statements-few-lines.cpp | 30 + ...ity-braces-around-statements-same-line.cpp | 36 + ...y-braces-around-statements-single-line.cpp | 33 + .../readability-braces-around-statements.cpp | 194 ++ .../readability-container-size-empty.cpp | 320 +++ .../readability-delete-null-pointer.cpp | 76 + .../readability-deleted-default.cpp | 127 + .../readability-else-after-return.cpp | 89 + test/clang-tidy/readability-function-size.cpp | 54 + .../readability-identifier-naming.cpp | 420 +++ ...icit-bool-cast-allow-conditional-casts.cpp | 55 + .../readability-implicit-bool-cast-cxx98.cpp | 45 + .../readability-implicit-bool-cast.cpp | 433 ++++ ...nconsistent-declaration-parameter-name.cpp | 188 ++ .../readability-misplaced-array-index.cpp | 34 + .../readability-named-parameter.cpp | 133 + .../readability-non-const-parameter.cpp | 279 ++ .../readability-redundant-control-flow.cpp | 224 ++ .../readability-redundant-declaration.cpp | 30 + ...ity-redundant-function-ptr-dereference.cpp | 24 + .../readability-redundant-member-init.cpp | 199 ++ .../readability-redundant-smartptr-get.cpp | 172 ++ ...readability-redundant-string-cstr-msvc.cpp | 47 + .../readability-redundant-string-cstr.cpp | 207 ++ ...readability-redundant-string-init-msvc.cpp | 61 + .../readability-redundant-string-init.cpp | 140 + ...ol-expr-chained-conditional-assignment.cpp | 34 + ...y-bool-expr-chained-conditional-return.cpp | 94 + .../readability-simplify-bool-expr.cpp | 932 +++++++ ...atic-definition-in-anonymous-namespace.cpp | 49 + .../readability-uniqueptr-delete-release.cpp | 71 + test/clang-tidy/select-checks.cpp | 11 + test/clang-tidy/serialize-diagnostics.cpp | 3 + test/clang-tidy/static-analyzer-config.cpp | 19 + test/clang-tidy/static-analyzer.cpp | 17 + test/clang-tidy/temporaries.cpp | 23 + test/clang-tidy/validate-check-names.cpp | 2 + test/clang-tidy/werrors-diagnostics.cpp | 13 + test/clang-tidy/werrors-plural.cpp | 18 + test/clang-tidy/werrors.cpp | 10 + .../Inputs/database_template.json | 7 + test/include-fixer/Inputs/fake_yaml_db.yaml | 60 + test/include-fixer/Inputs/merge/a.yaml | 20 + test/include-fixer/Inputs/merge/b.yaml | 20 + test/include-fixer/commandline_options.cpp | 15 + test/include-fixer/exit_on_fatal.cpp | 10 + test/include-fixer/fixeddb.cpp | 8 + test/include-fixer/include_path.cpp | 19 + test/include-fixer/merge.test | 33 + test/include-fixer/multiple_fixes.cpp | 13 + test/include-fixer/prefix_variable.cpp | 10 + test/include-fixer/query_symbol.cpp | 13 + test/include-fixer/ranking.cpp | 13 + test/include-fixer/yamldb.cpp | 8 + test/include-fixer/yamldb_autodetect.cpp | 11 + test/include-fixer/yamldb_plugin.cpp | 24 + test/lit.cfg | 201 ++ test/lit.site.cfg.in | 25 + test/modularize/Inputs/Anonymous.h | 11 + .../modularize/Inputs/CompileError/HasError.h | 2 + test/modularize/Inputs/CompileError/Level1A.h | 1 + .../Inputs/CompileError/module.modulemap | 10 + .../Includes1/.hidden/DontFindMe.h | 3 + .../CoverageNoProblems/Includes1/Level1A.h | 1 + .../CoverageNoProblems/Includes2/Level2A.h | 1 + .../CoverageNoProblems/NonIncludes/Level3A.h | 1 + .../CoverageNoProblems/module.modulemap | 10 + .../Inputs/CoverageProblems/Level1A.h | 2 + .../Inputs/CoverageProblems/Level1B.h | 2 + .../Inputs/CoverageProblems/Level2A.h | 1 + .../Inputs/CoverageProblems/Level2B.h | 1 + .../Inputs/CoverageProblems/Level3A.h | 2 + .../Inputs/CoverageProblems/Level3B | 1 + .../Inputs/CoverageProblems/Sub/Level3B.h | 1 + .../Inputs/CoverageProblems/UmbrellaFile.h | 3 + .../CoverageProblems/UmbrellaInclude1.h | 1 + .../CoverageProblems/UmbrellaInclude2.h | 1 + .../CoverageProblems/UmbrellaSub/Umbrell1.h | 1 + .../CoverageProblems/UmbrellaSub/Umbrell2.h | 1 + .../Inputs/CoverageProblems/module.modulemap | 30 + test/modularize/Inputs/DuplicateHeader1.h | 2 + test/modularize/Inputs/DuplicateHeader2.h | 2 + test/modularize/Inputs/Empty.h | 1 + test/modularize/Inputs/HeaderGuard.h | 6 + test/modularize/Inputs/HeaderGuardSub1.h | 5 + test/modularize/Inputs/HeaderGuardSub2.h | 5 + test/modularize/Inputs/HeaderGuardSubSub.h | 9 + .../Inputs/HeaderGuardSubSubDefined.h | 9 + test/modularize/Inputs/IncludeInExtern.h | 3 + test/modularize/Inputs/IncludeInNamespace.h | 3 + test/modularize/Inputs/InconsistentHeader1.h | 4 + test/modularize/Inputs/InconsistentHeader2.h | 3 + .../modularize/Inputs/InconsistentSubHeader.h | 18 + test/modularize/Inputs/IsDependent.h | 4 + .../modularize/Inputs/MissingHeader/Level1A.h | 1 + .../Inputs/MissingHeader/module.modulemap | 10 + test/modularize/Inputs/NamespaceClasses.h | 20 + test/modularize/Inputs/NestedMacro.h | 5 + test/modularize/Inputs/NoProblems.modulemap | 9 + .../Inputs/ProblemsDuplicate.modulemap | 9 + test/modularize/Inputs/SomeDecls.h | 16 + test/modularize/Inputs/SomeOtherTypes.h | 4 + test/modularize/Inputs/SomeTypes.h | 16 + test/modularize/Inputs/SubModule1/Header1.h | 1 + test/modularize/Inputs/SubModule1/Header2.h | 1 + test/modularize/Inputs/SubModule2/Header3.h | 1 + test/modularize/Inputs/SubModule2/Header4.h | 1 + test/modularize/Inputs/TemplateClasses.h | 15 + test/modularize/NoProblems.modularize | 6 + .../modularize/NoProblemsAnonymous.modularize | 3 + .../modularize/NoProblemsAssistant.modularize | 50 + test/modularize/NoProblemsCoverage.modularize | 1 + .../NoProblemsDependencies.modularize | 3 + test/modularize/NoProblemsGuard.modularize | 5 + test/modularize/NoProblemsList.modularize | 2 + .../modularize/NoProblemsNamespace.modularize | 3 + .../NoProblemsNamespaceClasses.modularize | 3 + .../NoProblemsNestedMacro.modularize | 3 + .../NoProblemsTemplateClasses.modularize | 3 + .../ProblemsCompileError.modularize | 3 + test/modularize/ProblemsCoverage.modularize | 5 + .../ProblemsDisplayLists.modularize | 16 + test/modularize/ProblemsDuplicate.modularize | 9 + test/modularize/ProblemsExternC.modularize | 12 + .../ProblemsInconsistent.modularize | 108 + .../ProblemsMissingHeader.modularize | 3 + test/modularize/ProblemsNamespace.modularize | 12 + test/modularize/SubModule2.h | 3 + test/pp-trace/Inputs/Level1A.h | 2 + test/pp-trace/Inputs/Level1B.h | 2 + test/pp-trace/Inputs/Level2A.h | 1 + test/pp-trace/Inputs/Level2B.h | 1 + test/pp-trace/Inputs/ModularizeList.txt | 4 + test/pp-trace/Inputs/module.map | 18 + test/pp-trace/pp-trace-conditional.cpp | 304 +++ test/pp-trace/pp-trace-ident.cpp | 10 + test/pp-trace/pp-trace-include.cpp | 141 + test/pp-trace/pp-trace-macro.cpp | 101 + test/pp-trace/pp-trace-modules.cpp | 20 + test/pp-trace/pp-trace-pragma-general.cpp | 107 + test/pp-trace/pp-trace-pragma-ms.cpp | 100 + test/pp-trace/pp-trace-pragma-opencl.cpp | 33 + tool-template/CMakeLists.txt | 15 + tool-template/ToolTemplate.cpp | 90 + unittests/CMakeLists.txt | 13 + unittests/change-namespace/CMakeLists.txt | 28 + .../change-namespace/ChangeNamespaceTests.cpp | 1604 ++++++++++++ .../clang-apply-replacements/CMakeLists.txt | 17 + .../ReformattingTest.cpp | 67 + unittests/clang-move/CMakeLists.txt | 28 + unittests/clang-move/ClangMoveTests.cpp | 568 ++++ unittests/clang-query/CMakeLists.txt | 22 + unittests/clang-query/QueryEngineTest.cpp | 146 ++ unittests/clang-query/QueryParserTest.cpp | 158 ++ unittests/clang-tidy/CMakeLists.txt | 35 + .../ClangTidyDiagnosticConsumerTest.cpp | 87 + unittests/clang-tidy/ClangTidyOptionsTest.cpp | 104 + unittests/clang-tidy/ClangTidyTest.h | 151 ++ unittests/clang-tidy/GoogleModuleTest.cpp | 110 + unittests/clang-tidy/IncludeInserterTest.cpp | 612 +++++ unittests/clang-tidy/LLVMModuleTest.cpp | 189 ++ unittests/clang-tidy/MiscModuleTest.cpp | 39 + unittests/clang-tidy/NamespaceAliaserTest.cpp | 123 + .../OverlappingReplacementsTest.cpp | 409 +++ .../clang-tidy/ReadabilityModuleTest.cpp | 499 ++++ unittests/clang-tidy/UsingInserterTest.cpp | 114 + unittests/include-fixer/CMakeLists.txt | 29 + unittests/include-fixer/IncludeFixerTest.cpp | 363 +++ .../find-all-symbols/CMakeLists.txt | 23 + .../find-all-symbols/FindAllSymbolsTests.cpp | 481 ++++ unittests/include/common/VirtualFileHelper.h | 82 + 1154 files changed, 105733 insertions(+) create mode 100644 .arcconfig create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CODE_OWNERS.TXT create mode 100644 LICENSE.TXT create mode 100644 README.txt create mode 100644 change-namespace/CMakeLists.txt create mode 100644 change-namespace/ChangeNamespace.cpp create mode 100644 change-namespace/ChangeNamespace.h create mode 100644 change-namespace/tool/CMakeLists.txt create mode 100644 change-namespace/tool/ClangChangeNamespace.cpp create mode 100644 clang-apply-replacements/CMakeLists.txt create mode 100644 clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h create mode 100644 clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp create mode 100644 clang-apply-replacements/tool/CMakeLists.txt create mode 100644 clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp create mode 100644 clang-move/CMakeLists.txt create mode 100644 clang-move/ClangMove.cpp create mode 100644 clang-move/ClangMove.h create mode 100644 clang-move/HelperDeclRefGraph.cpp create mode 100644 clang-move/HelperDeclRefGraph.h create mode 100644 clang-move/tool/CMakeLists.txt create mode 100644 clang-move/tool/ClangMoveMain.cpp create mode 100644 clang-query/CMakeLists.txt create mode 100644 clang-query/Query.cpp create mode 100644 clang-query/Query.h create mode 100644 clang-query/QueryParser.cpp create mode 100644 clang-query/QueryParser.h create mode 100644 clang-query/QuerySession.h create mode 100644 clang-query/tool/CMakeLists.txt create mode 100644 clang-query/tool/ClangQuery.cpp create mode 100644 clang-rename/CMakeLists.txt create mode 100644 clang-rename/RenamingAction.cpp create mode 100644 clang-rename/RenamingAction.h create mode 100644 clang-rename/USRFinder.cpp create mode 100644 clang-rename/USRFinder.h create mode 100644 clang-rename/USRFindingAction.cpp create mode 100644 clang-rename/USRFindingAction.h create mode 100644 clang-rename/USRLocFinder.cpp create mode 100644 clang-rename/USRLocFinder.h create mode 100644 clang-rename/tool/CMakeLists.txt create mode 100644 clang-rename/tool/ClangRename.cpp create mode 100644 clang-rename/tool/clang-rename.el create mode 100644 clang-rename/tool/clang-rename.py create mode 100644 clang-reorder-fields/CMakeLists.txt create mode 100644 clang-reorder-fields/ReorderFieldsAction.cpp create mode 100644 clang-reorder-fields/ReorderFieldsAction.h create mode 100644 clang-reorder-fields/tool/CMakeLists.txt create mode 100644 clang-reorder-fields/tool/ClangReorderFields.cpp create mode 100644 clang-tidy-vs/.gitignore create mode 100644 clang-tidy-vs/CMakeLists.txt create mode 100644 clang-tidy-vs/ClangTidy.sln create mode 100644 clang-tidy-vs/ClangTidy/CategoryVerb.cs create mode 100644 clang-tidy-vs/ClangTidy/CheckDatabase.cs create mode 100644 clang-tidy-vs/ClangTidy/CheckTree.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidy.csproj create mode 100644 clang-tidy-vs/ClangTidy/ClangTidy.vsct create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyPackage.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyProperties.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs create mode 100644 clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx create mode 100644 clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs create mode 100644 clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs create mode 100644 clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs create mode 100644 clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs create mode 100644 clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs create mode 100644 clang-tidy-vs/ClangTidy/GlobalSuppressions.cs create mode 100644 clang-tidy-vs/ClangTidy/Guids.cs create mode 100644 clang-tidy-vs/ClangTidy/PkgCmdID.cs create mode 100644 clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs create mode 100644 clang-tidy-vs/ClangTidy/Resources.Designer.cs create mode 100644 clang-tidy-vs/ClangTidy/Resources.resx create mode 100644 clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml create mode 100644 clang-tidy-vs/ClangTidy/Resources/Images_32bit.bmp create mode 100644 clang-tidy-vs/ClangTidy/Resources/Package.ico create mode 100644 clang-tidy-vs/ClangTidy/Utility.cs create mode 100644 clang-tidy-vs/ClangTidy/VSPackage.resx create mode 100644 clang-tidy-vs/ClangTidy/license.txt create mode 100644 clang-tidy-vs/ClangTidy/packages.config create mode 100644 clang-tidy-vs/ClangTidy/source.extension.vsixmanifest create mode 100644 clang-tidy-vs/README.txt create mode 100644 clang-tidy-vs/source.extension.vsixmanifest.in create mode 100644 clang-tidy/CMakeLists.txt create mode 100644 clang-tidy/ClangTidy.cpp create mode 100644 clang-tidy/ClangTidy.h create mode 100644 clang-tidy/ClangTidyDiagnosticConsumer.cpp create mode 100644 clang-tidy/ClangTidyDiagnosticConsumer.h create mode 100644 clang-tidy/ClangTidyModule.cpp create mode 100644 clang-tidy/ClangTidyModule.h create mode 100644 clang-tidy/ClangTidyModuleRegistry.h create mode 100644 clang-tidy/ClangTidyOptions.cpp create mode 100644 clang-tidy/ClangTidyOptions.h create mode 100755 clang-tidy/add_new_check.py create mode 100644 clang-tidy/boost/BoostTidyModule.cpp create mode 100644 clang-tidy/boost/CMakeLists.txt create mode 100644 clang-tidy/boost/UseToStringCheck.cpp create mode 100644 clang-tidy/boost/UseToStringCheck.h create mode 100644 clang-tidy/cert/CERTTidyModule.cpp create mode 100644 clang-tidy/cert/CMakeLists.txt create mode 100644 clang-tidy/cert/CommandProcessorCheck.cpp create mode 100644 clang-tidy/cert/CommandProcessorCheck.h create mode 100644 clang-tidy/cert/FloatLoopCounter.cpp create mode 100644 clang-tidy/cert/FloatLoopCounter.h create mode 100644 clang-tidy/cert/LICENSE.TXT create mode 100644 clang-tidy/cert/LimitedRandomnessCheck.cpp create mode 100644 clang-tidy/cert/LimitedRandomnessCheck.h create mode 100644 clang-tidy/cert/SetLongJmpCheck.cpp create mode 100644 clang-tidy/cert/SetLongJmpCheck.h create mode 100644 clang-tidy/cert/StaticObjectExceptionCheck.cpp create mode 100644 clang-tidy/cert/StaticObjectExceptionCheck.h create mode 100644 clang-tidy/cert/StrToNumCheck.cpp create mode 100644 clang-tidy/cert/StrToNumCheck.h create mode 100644 clang-tidy/cert/ThrownExceptionTypeCheck.cpp create mode 100644 clang-tidy/cert/ThrownExceptionTypeCheck.h create mode 100644 clang-tidy/cert/VariadicFunctionDefCheck.cpp create mode 100644 clang-tidy/cert/VariadicFunctionDefCheck.h create mode 100644 clang-tidy/cppcoreguidelines/CMakeLists.txt create mode 100644 clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp create mode 100644 clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h create mode 100644 clang-tidy/cppcoreguidelines/NoMallocCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/NoMallocCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeReinterpretCastCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeStaticCastDowncastCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeUnionAccessCheck.h create mode 100644 clang-tidy/cppcoreguidelines/ProTypeVarargCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/ProTypeVarargCheck.h create mode 100644 clang-tidy/cppcoreguidelines/SlicingCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/SlicingCheck.h create mode 100644 clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp create mode 100644 clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h create mode 100644 clang-tidy/google/AvoidCStyleCastsCheck.cpp create mode 100644 clang-tidy/google/AvoidCStyleCastsCheck.h create mode 100644 clang-tidy/google/CMakeLists.txt create mode 100644 clang-tidy/google/DefaultArgumentsCheck.cpp create mode 100644 clang-tidy/google/DefaultArgumentsCheck.h create mode 100644 clang-tidy/google/ExplicitConstructorCheck.cpp create mode 100644 clang-tidy/google/ExplicitConstructorCheck.h create mode 100644 clang-tidy/google/ExplicitMakePairCheck.cpp create mode 100644 clang-tidy/google/ExplicitMakePairCheck.h create mode 100644 clang-tidy/google/GlobalNamesInHeadersCheck.cpp create mode 100644 clang-tidy/google/GlobalNamesInHeadersCheck.h create mode 100644 clang-tidy/google/GoogleTidyModule.cpp create mode 100644 clang-tidy/google/IntegerTypesCheck.cpp create mode 100644 clang-tidy/google/IntegerTypesCheck.h create mode 100644 clang-tidy/google/MemsetZeroLengthCheck.cpp create mode 100644 clang-tidy/google/MemsetZeroLengthCheck.h create mode 100644 clang-tidy/google/NonConstReferences.cpp create mode 100644 clang-tidy/google/NonConstReferences.h create mode 100644 clang-tidy/google/OverloadedUnaryAndCheck.cpp create mode 100644 clang-tidy/google/OverloadedUnaryAndCheck.h create mode 100644 clang-tidy/google/StringReferenceMemberCheck.cpp create mode 100644 clang-tidy/google/StringReferenceMemberCheck.h create mode 100644 clang-tidy/google/TodoCommentCheck.cpp create mode 100644 clang-tidy/google/TodoCommentCheck.h create mode 100644 clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp create mode 100644 clang-tidy/google/UnnamedNamespaceInHeaderCheck.h create mode 100644 clang-tidy/google/UsingNamespaceDirectiveCheck.cpp create mode 100644 clang-tidy/google/UsingNamespaceDirectiveCheck.h create mode 100644 clang-tidy/llvm/CMakeLists.txt create mode 100644 clang-tidy/llvm/HeaderGuardCheck.cpp create mode 100644 clang-tidy/llvm/HeaderGuardCheck.h create mode 100644 clang-tidy/llvm/IncludeOrderCheck.cpp create mode 100644 clang-tidy/llvm/IncludeOrderCheck.h create mode 100644 clang-tidy/llvm/LLVMTidyModule.cpp create mode 100644 clang-tidy/llvm/TwineLocalCheck.cpp create mode 100644 clang-tidy/llvm/TwineLocalCheck.h create mode 100644 clang-tidy/misc/ArgumentCommentCheck.cpp create mode 100644 clang-tidy/misc/ArgumentCommentCheck.h create mode 100644 clang-tidy/misc/AssertSideEffectCheck.cpp create mode 100644 clang-tidy/misc/AssertSideEffectCheck.h create mode 100644 clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp create mode 100644 clang-tidy/misc/BoolPointerImplicitConversionCheck.h create mode 100644 clang-tidy/misc/CMakeLists.txt create mode 100644 clang-tidy/misc/DanglingHandleCheck.cpp create mode 100644 clang-tidy/misc/DanglingHandleCheck.h create mode 100644 clang-tidy/misc/DefinitionsInHeadersCheck.cpp create mode 100644 clang-tidy/misc/DefinitionsInHeadersCheck.h create mode 100644 clang-tidy/misc/FoldInitTypeCheck.cpp create mode 100644 clang-tidy/misc/FoldInitTypeCheck.h create mode 100644 clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp create mode 100644 clang-tidy/misc/ForwardDeclarationNamespaceCheck.h create mode 100644 clang-tidy/misc/InaccurateEraseCheck.cpp create mode 100644 clang-tidy/misc/InaccurateEraseCheck.h create mode 100644 clang-tidy/misc/IncorrectRoundings.cpp create mode 100644 clang-tidy/misc/IncorrectRoundings.h create mode 100644 clang-tidy/misc/InefficientAlgorithmCheck.cpp create mode 100644 clang-tidy/misc/InefficientAlgorithmCheck.h create mode 100644 clang-tidy/misc/MacroParenthesesCheck.cpp create mode 100644 clang-tidy/misc/MacroParenthesesCheck.h create mode 100644 clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp create mode 100644 clang-tidy/misc/MacroRepeatedSideEffectsCheck.h create mode 100644 clang-tidy/misc/MiscTidyModule.cpp create mode 100644 clang-tidy/misc/MisplacedConstCheck.cpp create mode 100644 clang-tidy/misc/MisplacedConstCheck.h create mode 100644 clang-tidy/misc/MisplacedWideningCastCheck.cpp create mode 100644 clang-tidy/misc/MisplacedWideningCastCheck.h create mode 100644 clang-tidy/misc/MoveConstantArgumentCheck.cpp create mode 100644 clang-tidy/misc/MoveConstantArgumentCheck.h create mode 100644 clang-tidy/misc/MoveConstructorInitCheck.cpp create mode 100644 clang-tidy/misc/MoveConstructorInitCheck.h create mode 100644 clang-tidy/misc/MoveForwardingReferenceCheck.cpp create mode 100644 clang-tidy/misc/MoveForwardingReferenceCheck.h create mode 100644 clang-tidy/misc/MultipleStatementMacroCheck.cpp create mode 100644 clang-tidy/misc/MultipleStatementMacroCheck.h create mode 100644 clang-tidy/misc/NewDeleteOverloadsCheck.cpp create mode 100644 clang-tidy/misc/NewDeleteOverloadsCheck.h create mode 100644 clang-tidy/misc/NoexceptMoveConstructorCheck.cpp create mode 100644 clang-tidy/misc/NoexceptMoveConstructorCheck.h create mode 100644 clang-tidy/misc/NonCopyableObjects.cpp create mode 100644 clang-tidy/misc/NonCopyableObjects.h create mode 100644 clang-tidy/misc/RedundantExpressionCheck.cpp create mode 100644 clang-tidy/misc/RedundantExpressionCheck.h create mode 100644 clang-tidy/misc/SizeofContainerCheck.cpp create mode 100644 clang-tidy/misc/SizeofContainerCheck.h create mode 100644 clang-tidy/misc/SizeofExpressionCheck.cpp create mode 100644 clang-tidy/misc/SizeofExpressionCheck.h create mode 100644 clang-tidy/misc/StaticAssertCheck.cpp create mode 100644 clang-tidy/misc/StaticAssertCheck.h create mode 100644 clang-tidy/misc/StringCompareCheck.cpp create mode 100644 clang-tidy/misc/StringCompareCheck.h create mode 100644 clang-tidy/misc/StringConstructorCheck.cpp create mode 100644 clang-tidy/misc/StringConstructorCheck.h create mode 100644 clang-tidy/misc/StringIntegerAssignmentCheck.cpp create mode 100644 clang-tidy/misc/StringIntegerAssignmentCheck.h create mode 100644 clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp create mode 100644 clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h create mode 100644 clang-tidy/misc/SuspiciousEnumUsageCheck.cpp create mode 100644 clang-tidy/misc/SuspiciousEnumUsageCheck.h create mode 100644 clang-tidy/misc/SuspiciousMissingCommaCheck.cpp create mode 100644 clang-tidy/misc/SuspiciousMissingCommaCheck.h create mode 100644 clang-tidy/misc/SuspiciousSemicolonCheck.cpp create mode 100644 clang-tidy/misc/SuspiciousSemicolonCheck.h create mode 100644 clang-tidy/misc/SuspiciousStringCompareCheck.cpp create mode 100644 clang-tidy/misc/SuspiciousStringCompareCheck.h create mode 100644 clang-tidy/misc/SwappedArgumentsCheck.cpp create mode 100644 clang-tidy/misc/SwappedArgumentsCheck.h create mode 100644 clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp create mode 100644 clang-tidy/misc/ThrowByValueCatchByReferenceCheck.h create mode 100644 clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp create mode 100644 clang-tidy/misc/UnconventionalAssignOperatorCheck.h create mode 100644 clang-tidy/misc/UndelegatedConstructor.cpp create mode 100644 clang-tidy/misc/UndelegatedConstructor.h create mode 100644 clang-tidy/misc/UniqueptrResetReleaseCheck.cpp create mode 100644 clang-tidy/misc/UniqueptrResetReleaseCheck.h create mode 100644 clang-tidy/misc/UnusedAliasDeclsCheck.cpp create mode 100644 clang-tidy/misc/UnusedAliasDeclsCheck.h create mode 100644 clang-tidy/misc/UnusedParametersCheck.cpp create mode 100644 clang-tidy/misc/UnusedParametersCheck.h create mode 100644 clang-tidy/misc/UnusedRAIICheck.cpp create mode 100644 clang-tidy/misc/UnusedRAIICheck.h create mode 100644 clang-tidy/misc/UnusedUsingDeclsCheck.cpp create mode 100644 clang-tidy/misc/UnusedUsingDeclsCheck.h create mode 100644 clang-tidy/misc/UseAfterMoveCheck.cpp create mode 100644 clang-tidy/misc/UseAfterMoveCheck.h create mode 100644 clang-tidy/misc/VirtualNearMissCheck.cpp create mode 100644 clang-tidy/misc/VirtualNearMissCheck.h create mode 100644 clang-tidy/modernize/AvoidBindCheck.cpp create mode 100644 clang-tidy/modernize/AvoidBindCheck.h create mode 100644 clang-tidy/modernize/CMakeLists.txt create mode 100644 clang-tidy/modernize/DeprecatedHeadersCheck.cpp create mode 100644 clang-tidy/modernize/DeprecatedHeadersCheck.h create mode 100644 clang-tidy/modernize/LoopConvertCheck.cpp create mode 100644 clang-tidy/modernize/LoopConvertCheck.h create mode 100644 clang-tidy/modernize/LoopConvertUtils.cpp create mode 100644 clang-tidy/modernize/LoopConvertUtils.h create mode 100644 clang-tidy/modernize/MakeSharedCheck.cpp create mode 100644 clang-tidy/modernize/MakeSharedCheck.h create mode 100644 clang-tidy/modernize/MakeSmartPtrCheck.cpp create mode 100644 clang-tidy/modernize/MakeSmartPtrCheck.h create mode 100644 clang-tidy/modernize/MakeUniqueCheck.cpp create mode 100644 clang-tidy/modernize/MakeUniqueCheck.h create mode 100644 clang-tidy/modernize/ModernizeTidyModule.cpp create mode 100644 clang-tidy/modernize/PassByValueCheck.cpp create mode 100644 clang-tidy/modernize/PassByValueCheck.h create mode 100644 clang-tidy/modernize/RawStringLiteralCheck.cpp create mode 100644 clang-tidy/modernize/RawStringLiteralCheck.h create mode 100644 clang-tidy/modernize/RedundantVoidArgCheck.cpp create mode 100644 clang-tidy/modernize/RedundantVoidArgCheck.h create mode 100644 clang-tidy/modernize/ReplaceAutoPtrCheck.cpp create mode 100644 clang-tidy/modernize/ReplaceAutoPtrCheck.h create mode 100644 clang-tidy/modernize/ShrinkToFitCheck.cpp create mode 100644 clang-tidy/modernize/ShrinkToFitCheck.h create mode 100644 clang-tidy/modernize/UseAutoCheck.cpp create mode 100644 clang-tidy/modernize/UseAutoCheck.h create mode 100644 clang-tidy/modernize/UseBoolLiteralsCheck.cpp create mode 100644 clang-tidy/modernize/UseBoolLiteralsCheck.h create mode 100644 clang-tidy/modernize/UseDefaultMemberInitCheck.cpp create mode 100644 clang-tidy/modernize/UseDefaultMemberInitCheck.h create mode 100644 clang-tidy/modernize/UseEmplaceCheck.cpp create mode 100644 clang-tidy/modernize/UseEmplaceCheck.h create mode 100644 clang-tidy/modernize/UseEqualsDefaultCheck.cpp create mode 100644 clang-tidy/modernize/UseEqualsDefaultCheck.h create mode 100644 clang-tidy/modernize/UseEqualsDeleteCheck.cpp create mode 100644 clang-tidy/modernize/UseEqualsDeleteCheck.h create mode 100644 clang-tidy/modernize/UseNullptrCheck.cpp create mode 100644 clang-tidy/modernize/UseNullptrCheck.h create mode 100644 clang-tidy/modernize/UseOverrideCheck.cpp create mode 100644 clang-tidy/modernize/UseOverrideCheck.h create mode 100644 clang-tidy/modernize/UseTransparentFunctorsCheck.cpp create mode 100644 clang-tidy/modernize/UseTransparentFunctorsCheck.h create mode 100644 clang-tidy/modernize/UseUsingCheck.cpp create mode 100644 clang-tidy/modernize/UseUsingCheck.h create mode 100644 clang-tidy/mpi/BufferDerefCheck.cpp create mode 100644 clang-tidy/mpi/BufferDerefCheck.h create mode 100644 clang-tidy/mpi/CMakeLists.txt create mode 100644 clang-tidy/mpi/MPITidyModule.cpp create mode 100644 clang-tidy/mpi/TypeMismatchCheck.cpp create mode 100644 clang-tidy/mpi/TypeMismatchCheck.h create mode 100644 clang-tidy/performance/CMakeLists.txt create mode 100644 clang-tidy/performance/FasterStringFindCheck.cpp create mode 100644 clang-tidy/performance/FasterStringFindCheck.h create mode 100644 clang-tidy/performance/ForRangeCopyCheck.cpp create mode 100644 clang-tidy/performance/ForRangeCopyCheck.h create mode 100644 clang-tidy/performance/ImplicitCastInLoopCheck.cpp create mode 100644 clang-tidy/performance/ImplicitCastInLoopCheck.h create mode 100644 clang-tidy/performance/InefficientStringConcatenationCheck.cpp create mode 100644 clang-tidy/performance/InefficientStringConcatenationCheck.h create mode 100644 clang-tidy/performance/PerformanceTidyModule.cpp create mode 100644 clang-tidy/performance/TypePromotionInMathFnCheck.cpp create mode 100644 clang-tidy/performance/TypePromotionInMathFnCheck.h create mode 100644 clang-tidy/performance/UnnecessaryCopyInitialization.cpp create mode 100644 clang-tidy/performance/UnnecessaryCopyInitialization.h create mode 100644 clang-tidy/performance/UnnecessaryValueParamCheck.cpp create mode 100644 clang-tidy/performance/UnnecessaryValueParamCheck.h create mode 100644 clang-tidy/plugin/CMakeLists.txt create mode 100644 clang-tidy/plugin/ClangTidyPlugin.cpp create mode 100644 clang-tidy/readability/AvoidConstParamsInDecls.cpp create mode 100644 clang-tidy/readability/AvoidConstParamsInDecls.h create mode 100644 clang-tidy/readability/BracesAroundStatementsCheck.cpp create mode 100644 clang-tidy/readability/BracesAroundStatementsCheck.h create mode 100644 clang-tidy/readability/CMakeLists.txt create mode 100644 clang-tidy/readability/ContainerSizeEmptyCheck.cpp create mode 100644 clang-tidy/readability/ContainerSizeEmptyCheck.h create mode 100644 clang-tidy/readability/DeleteNullPointerCheck.cpp create mode 100644 clang-tidy/readability/DeleteNullPointerCheck.h create mode 100644 clang-tidy/readability/DeletedDefaultCheck.cpp create mode 100644 clang-tidy/readability/DeletedDefaultCheck.h create mode 100644 clang-tidy/readability/ElseAfterReturnCheck.cpp create mode 100644 clang-tidy/readability/ElseAfterReturnCheck.h create mode 100644 clang-tidy/readability/FunctionSizeCheck.cpp create mode 100644 clang-tidy/readability/FunctionSizeCheck.h create mode 100644 clang-tidy/readability/IdentifierNamingCheck.cpp create mode 100644 clang-tidy/readability/IdentifierNamingCheck.h create mode 100644 clang-tidy/readability/ImplicitBoolCastCheck.cpp create mode 100644 clang-tidy/readability/ImplicitBoolCastCheck.h create mode 100644 clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp create mode 100644 clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h create mode 100644 clang-tidy/readability/MisplacedArrayIndexCheck.cpp create mode 100644 clang-tidy/readability/MisplacedArrayIndexCheck.h create mode 100644 clang-tidy/readability/NamedParameterCheck.cpp create mode 100644 clang-tidy/readability/NamedParameterCheck.h create mode 100644 clang-tidy/readability/NamespaceCommentCheck.cpp create mode 100644 clang-tidy/readability/NamespaceCommentCheck.h create mode 100644 clang-tidy/readability/NonConstParameterCheck.cpp create mode 100644 clang-tidy/readability/NonConstParameterCheck.h create mode 100644 clang-tidy/readability/ReadabilityTidyModule.cpp create mode 100644 clang-tidy/readability/RedundantControlFlowCheck.cpp create mode 100644 clang-tidy/readability/RedundantControlFlowCheck.h create mode 100644 clang-tidy/readability/RedundantDeclarationCheck.cpp create mode 100644 clang-tidy/readability/RedundantDeclarationCheck.h create mode 100644 clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp create mode 100644 clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h create mode 100644 clang-tidy/readability/RedundantMemberInitCheck.cpp create mode 100644 clang-tidy/readability/RedundantMemberInitCheck.h create mode 100644 clang-tidy/readability/RedundantSmartptrGetCheck.cpp create mode 100644 clang-tidy/readability/RedundantSmartptrGetCheck.h create mode 100644 clang-tidy/readability/RedundantStringCStrCheck.cpp create mode 100644 clang-tidy/readability/RedundantStringCStrCheck.h create mode 100644 clang-tidy/readability/RedundantStringInitCheck.cpp create mode 100644 clang-tidy/readability/RedundantStringInitCheck.h create mode 100644 clang-tidy/readability/SimplifyBooleanExprCheck.cpp create mode 100644 clang-tidy/readability/SimplifyBooleanExprCheck.h create mode 100644 clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp create mode 100644 clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h create mode 100644 clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp create mode 100644 clang-tidy/readability/UniqueptrDeleteReleaseCheck.h create mode 100755 clang-tidy/rename_check.py create mode 100644 clang-tidy/tool/CMakeLists.txt create mode 100644 clang-tidy/tool/ClangTidyMain.cpp create mode 100755 clang-tidy/tool/clang-tidy-diff.py create mode 100755 clang-tidy/tool/run-clang-tidy.py create mode 100644 clang-tidy/utils/ASTUtils.cpp create mode 100644 clang-tidy/utils/ASTUtils.h create mode 100644 clang-tidy/utils/CMakeLists.txt create mode 100644 clang-tidy/utils/DeclRefExprUtils.cpp create mode 100644 clang-tidy/utils/DeclRefExprUtils.h create mode 100644 clang-tidy/utils/ExprSequence.cpp create mode 100644 clang-tidy/utils/ExprSequence.h create mode 100644 clang-tidy/utils/FixItHintUtils.cpp create mode 100644 clang-tidy/utils/FixItHintUtils.h create mode 100644 clang-tidy/utils/HeaderFileExtensionsUtils.cpp create mode 100644 clang-tidy/utils/HeaderFileExtensionsUtils.h create mode 100644 clang-tidy/utils/HeaderGuard.cpp create mode 100644 clang-tidy/utils/HeaderGuard.h create mode 100644 clang-tidy/utils/IncludeInserter.cpp create mode 100644 clang-tidy/utils/IncludeInserter.h create mode 100644 clang-tidy/utils/IncludeSorter.cpp create mode 100644 clang-tidy/utils/IncludeSorter.h create mode 100644 clang-tidy/utils/LexerUtils.cpp create mode 100644 clang-tidy/utils/LexerUtils.h create mode 100644 clang-tidy/utils/Matchers.h create mode 100644 clang-tidy/utils/NamespaceAliaser.cpp create mode 100644 clang-tidy/utils/NamespaceAliaser.h create mode 100644 clang-tidy/utils/OptionsUtils.cpp create mode 100644 clang-tidy/utils/OptionsUtils.h create mode 100644 clang-tidy/utils/TypeTraits.cpp create mode 100644 clang-tidy/utils/TypeTraits.h create mode 100644 clang-tidy/utils/UsingInserter.cpp create mode 100644 clang-tidy/utils/UsingInserter.h create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile create mode 100644 docs/ModularizeUsage.rst create mode 100644 docs/README.txt create mode 100644 docs/ReleaseNotes.rst create mode 100644 docs/clang-modernize.rst create mode 100644 docs/clang-rename.rst create mode 100644 docs/clang-tidy.rst create mode 100644 docs/clang-tidy/checks/boost-use-to-string.rst create mode 100644 docs/clang-tidy/checks/cert-dcl03-c.rst create mode 100644 docs/clang-tidy/checks/cert-dcl50-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-dcl54-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-dcl59-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-env33-c.rst create mode 100644 docs/clang-tidy/checks/cert-err09-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-err34-c.rst create mode 100644 docs/clang-tidy/checks/cert-err52-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-err58-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-err60-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-err61-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-fio38-c.rst create mode 100644 docs/clang-tidy/checks/cert-flp30-c.rst create mode 100644 docs/clang-tidy/checks/cert-msc30-c.rst create mode 100644 docs/clang-tidy/checks/cert-msc50-cpp.rst create mode 100644 docs/clang-tidy/checks/cert-oop11-cpp.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-member-init.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-reinterpret-cast.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-static-cast-downcast.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-union-access.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-pro-type-vararg.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-slicing.rst create mode 100644 docs/clang-tidy/checks/cppcoreguidelines-special-member-functions.rst create mode 100644 docs/clang-tidy/checks/google-build-explicit-make-pair.rst create mode 100644 docs/clang-tidy/checks/google-build-namespaces.rst create mode 100644 docs/clang-tidy/checks/google-build-using-namespace.rst create mode 100644 docs/clang-tidy/checks/google-default-arguments.rst create mode 100644 docs/clang-tidy/checks/google-explicit-constructor.rst create mode 100644 docs/clang-tidy/checks/google-global-names-in-headers.rst create mode 100644 docs/clang-tidy/checks/google-readability-braces-around-statements.rst create mode 100644 docs/clang-tidy/checks/google-readability-casting.rst create mode 100644 docs/clang-tidy/checks/google-readability-function-size.rst create mode 100644 docs/clang-tidy/checks/google-readability-namespace-comments.rst create mode 100644 docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst create mode 100644 docs/clang-tidy/checks/google-readability-todo.rst create mode 100644 docs/clang-tidy/checks/google-runtime-int.rst create mode 100644 docs/clang-tidy/checks/google-runtime-member-string-references.rst create mode 100644 docs/clang-tidy/checks/google-runtime-memset.rst create mode 100644 docs/clang-tidy/checks/google-runtime-operator.rst create mode 100644 docs/clang-tidy/checks/google-runtime-references.rst create mode 100644 docs/clang-tidy/checks/list.rst create mode 100644 docs/clang-tidy/checks/llvm-header-guard.rst create mode 100644 docs/clang-tidy/checks/llvm-include-order.rst create mode 100644 docs/clang-tidy/checks/llvm-namespace-comment.rst create mode 100644 docs/clang-tidy/checks/llvm-twine-local.rst create mode 100644 docs/clang-tidy/checks/misc-argument-comment.rst create mode 100644 docs/clang-tidy/checks/misc-assert-side-effect.rst create mode 100644 docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst create mode 100644 docs/clang-tidy/checks/misc-dangling-handle.rst create mode 100644 docs/clang-tidy/checks/misc-definitions-in-headers.rst create mode 100644 docs/clang-tidy/checks/misc-fold-init-type.rst create mode 100644 docs/clang-tidy/checks/misc-forward-declaration-namespace.rst create mode 100644 docs/clang-tidy/checks/misc-inaccurate-erase.rst create mode 100644 docs/clang-tidy/checks/misc-incorrect-roundings.rst create mode 100644 docs/clang-tidy/checks/misc-inefficient-algorithm.rst create mode 100644 docs/clang-tidy/checks/misc-macro-parentheses.rst create mode 100644 docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst create mode 100644 docs/clang-tidy/checks/misc-misplaced-const.rst create mode 100644 docs/clang-tidy/checks/misc-misplaced-widening-cast.rst create mode 100644 docs/clang-tidy/checks/misc-move-const-arg.rst create mode 100644 docs/clang-tidy/checks/misc-move-constructor-init.rst create mode 100644 docs/clang-tidy/checks/misc-move-forwarding-reference.rst create mode 100644 docs/clang-tidy/checks/misc-multiple-statement-macro.rst create mode 100644 docs/clang-tidy/checks/misc-new-delete-overloads.rst create mode 100644 docs/clang-tidy/checks/misc-noexcept-move-constructor.rst create mode 100644 docs/clang-tidy/checks/misc-non-copyable-objects.rst create mode 100644 docs/clang-tidy/checks/misc-redundant-expression.rst create mode 100644 docs/clang-tidy/checks/misc-sizeof-container.rst create mode 100644 docs/clang-tidy/checks/misc-sizeof-expression.rst create mode 100644 docs/clang-tidy/checks/misc-static-assert.rst create mode 100644 docs/clang-tidy/checks/misc-string-compare.rst create mode 100644 docs/clang-tidy/checks/misc-string-constructor.rst create mode 100644 docs/clang-tidy/checks/misc-string-integer-assignment.rst create mode 100644 docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst create mode 100644 docs/clang-tidy/checks/misc-suspicious-enum-usage.rst create mode 100644 docs/clang-tidy/checks/misc-suspicious-missing-comma.rst create mode 100644 docs/clang-tidy/checks/misc-suspicious-semicolon.rst create mode 100644 docs/clang-tidy/checks/misc-suspicious-string-compare.rst create mode 100644 docs/clang-tidy/checks/misc-swapped-arguments.rst create mode 100644 docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst create mode 100644 docs/clang-tidy/checks/misc-unconventional-assign-operator.rst create mode 100644 docs/clang-tidy/checks/misc-undelegated-constructor.rst create mode 100644 docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst create mode 100644 docs/clang-tidy/checks/misc-unused-alias-decls.rst create mode 100644 docs/clang-tidy/checks/misc-unused-parameters.rst create mode 100644 docs/clang-tidy/checks/misc-unused-raii.rst create mode 100644 docs/clang-tidy/checks/misc-unused-using-decls.rst create mode 100644 docs/clang-tidy/checks/misc-use-after-move.rst create mode 100644 docs/clang-tidy/checks/misc-virtual-near-miss.rst create mode 100644 docs/clang-tidy/checks/modernize-avoid-bind.rst create mode 100644 docs/clang-tidy/checks/modernize-deprecated-headers.rst create mode 100644 docs/clang-tidy/checks/modernize-loop-convert.rst create mode 100644 docs/clang-tidy/checks/modernize-make-shared.rst create mode 100644 docs/clang-tidy/checks/modernize-make-unique.rst create mode 100644 docs/clang-tidy/checks/modernize-pass-by-value.rst create mode 100644 docs/clang-tidy/checks/modernize-raw-string-literal.rst create mode 100644 docs/clang-tidy/checks/modernize-redundant-void-arg.rst create mode 100644 docs/clang-tidy/checks/modernize-replace-auto-ptr.rst create mode 100644 docs/clang-tidy/checks/modernize-shrink-to-fit.rst create mode 100644 docs/clang-tidy/checks/modernize-use-auto.rst create mode 100644 docs/clang-tidy/checks/modernize-use-bool-literals.rst create mode 100644 docs/clang-tidy/checks/modernize-use-default-member-init.rst create mode 100644 docs/clang-tidy/checks/modernize-use-default.rst create mode 100644 docs/clang-tidy/checks/modernize-use-emplace.rst create mode 100644 docs/clang-tidy/checks/modernize-use-equals-default.rst create mode 100644 docs/clang-tidy/checks/modernize-use-equals-delete.rst create mode 100644 docs/clang-tidy/checks/modernize-use-nullptr.rst create mode 100644 docs/clang-tidy/checks/modernize-use-override.rst create mode 100644 docs/clang-tidy/checks/modernize-use-transparent-functors.rst create mode 100644 docs/clang-tidy/checks/modernize-use-using.rst create mode 100644 docs/clang-tidy/checks/mpi-buffer-deref.rst create mode 100644 docs/clang-tidy/checks/mpi-type-mismatch.rst create mode 100644 docs/clang-tidy/checks/performance-faster-string-find.rst create mode 100644 docs/clang-tidy/checks/performance-for-range-copy.rst create mode 100644 docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst create mode 100644 docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst create mode 100644 docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst create mode 100644 docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst create mode 100644 docs/clang-tidy/checks/performance-unnecessary-value-param.rst create mode 100644 docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst create mode 100644 docs/clang-tidy/checks/readability-braces-around-statements.rst create mode 100644 docs/clang-tidy/checks/readability-container-size-empty.rst create mode 100644 docs/clang-tidy/checks/readability-delete-null-pointer.rst create mode 100644 docs/clang-tidy/checks/readability-deleted-default.rst create mode 100644 docs/clang-tidy/checks/readability-else-after-return.rst create mode 100644 docs/clang-tidy/checks/readability-function-size.rst create mode 100644 docs/clang-tidy/checks/readability-identifier-naming.rst create mode 100644 docs/clang-tidy/checks/readability-implicit-bool-cast.rst create mode 100644 docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst create mode 100644 docs/clang-tidy/checks/readability-misplaced-array-index.rst create mode 100644 docs/clang-tidy/checks/readability-named-parameter.rst create mode 100644 docs/clang-tidy/checks/readability-non-const-parameter.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-control-flow.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-declaration.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-member-init.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-smartptr-get.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-string-cstr.rst create mode 100644 docs/clang-tidy/checks/readability-redundant-string-init.rst create mode 100644 docs/clang-tidy/checks/readability-simplify-boolean-expr.rst create mode 100644 docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst create mode 100644 docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst create mode 100644 docs/clang-tidy/index.rst create mode 100755 docs/clang-tidy/tools/dump_check_docs.py create mode 100644 docs/conf.py create mode 100644 docs/cpp11-migrate.rst create mode 100644 docs/doxygen-mainpage.dox create mode 100644 docs/doxygen.cfg.in create mode 100644 docs/include-fixer.rst create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/modularize.rst create mode 100644 docs/pp-trace.rst create mode 100644 include-fixer/CMakeLists.txt create mode 100644 include-fixer/InMemorySymbolIndex.cpp create mode 100644 include-fixer/InMemorySymbolIndex.h create mode 100644 include-fixer/IncludeFixer.cpp create mode 100644 include-fixer/IncludeFixer.h create mode 100644 include-fixer/IncludeFixerContext.cpp create mode 100644 include-fixer/IncludeFixerContext.h create mode 100644 include-fixer/SymbolIndex.h create mode 100644 include-fixer/SymbolIndexManager.cpp create mode 100644 include-fixer/SymbolIndexManager.h create mode 100644 include-fixer/YamlSymbolIndex.cpp create mode 100644 include-fixer/YamlSymbolIndex.h create mode 100644 include-fixer/find-all-symbols/CMakeLists.txt create mode 100644 include-fixer/find-all-symbols/FindAllMacros.cpp create mode 100644 include-fixer/find-all-symbols/FindAllMacros.h create mode 100644 include-fixer/find-all-symbols/FindAllSymbols.cpp create mode 100644 include-fixer/find-all-symbols/FindAllSymbols.h create mode 100644 include-fixer/find-all-symbols/FindAllSymbolsAction.cpp create mode 100644 include-fixer/find-all-symbols/FindAllSymbolsAction.h create mode 100644 include-fixer/find-all-symbols/HeaderMapCollector.cpp create mode 100644 include-fixer/find-all-symbols/HeaderMapCollector.h create mode 100644 include-fixer/find-all-symbols/PathConfig.cpp create mode 100644 include-fixer/find-all-symbols/PathConfig.h create mode 100644 include-fixer/find-all-symbols/PragmaCommentHandler.cpp create mode 100644 include-fixer/find-all-symbols/PragmaCommentHandler.h create mode 100644 include-fixer/find-all-symbols/STLPostfixHeaderMap.cpp create mode 100644 include-fixer/find-all-symbols/STLPostfixHeaderMap.h create mode 100644 include-fixer/find-all-symbols/SymbolInfo.cpp create mode 100644 include-fixer/find-all-symbols/SymbolInfo.h create mode 100644 include-fixer/find-all-symbols/SymbolReporter.h create mode 100644 include-fixer/find-all-symbols/tool/CMakeLists.txt create mode 100644 include-fixer/find-all-symbols/tool/FindAllSymbolsMain.cpp create mode 100755 include-fixer/find-all-symbols/tool/run-find-all-symbols.py create mode 100644 include-fixer/plugin/CMakeLists.txt create mode 100644 include-fixer/plugin/IncludeFixerPlugin.cpp create mode 100644 include-fixer/tool/CMakeLists.txt create mode 100644 include-fixer/tool/ClangIncludeFixer.cpp create mode 100644 include-fixer/tool/clang-include-fixer-test.el create mode 100644 include-fixer/tool/clang-include-fixer.el create mode 100644 include-fixer/tool/clang-include-fixer.py create mode 100644 modularize/CMakeLists.txt create mode 100644 modularize/CoverageChecker.cpp create mode 100644 modularize/CoverageChecker.h create mode 100644 modularize/Modularize.cpp create mode 100644 modularize/Modularize.h create mode 100644 modularize/ModularizeUtilities.cpp create mode 100644 modularize/ModularizeUtilities.h create mode 100644 modularize/ModuleAssistant.cpp create mode 100644 modularize/PreprocessorTracker.cpp create mode 100644 modularize/PreprocessorTracker.h create mode 100644 pp-trace/CMakeLists.txt create mode 100644 pp-trace/PPCallbacksTracker.cpp create mode 100644 pp-trace/PPCallbacksTracker.h create mode 100644 pp-trace/PPTrace.cpp create mode 100644 test/.clang-format create mode 100644 test/CMakeLists.txt create mode 100644 test/Unit/lit.cfg create mode 100644 test/Unit/lit.site.cfg.in create mode 100644 test/change-namespace/lambda-function.cpp create mode 100644 test/change-namespace/macro.cpp create mode 100644 test/change-namespace/simple-move.cpp create mode 100644 test/clang-apply-replacements/Inputs/basic/basic.h create mode 100644 test/clang-apply-replacements/Inputs/basic/file1.yaml create mode 100644 test/clang-apply-replacements/Inputs/basic/file2.yaml create mode 100644 test/clang-apply-replacements/Inputs/conflict/common.h create mode 100644 test/clang-apply-replacements/Inputs/conflict/expected.txt create mode 100644 test/clang-apply-replacements/Inputs/conflict/file1.yaml create mode 100644 test/clang-apply-replacements/Inputs/conflict/file2.yaml create mode 100644 test/clang-apply-replacements/Inputs/conflict/file3.yaml create mode 100644 test/clang-apply-replacements/Inputs/crlf/crlf.cpp create mode 100644 test/clang-apply-replacements/Inputs/crlf/crlf.cpp.expected create mode 100644 test/clang-apply-replacements/Inputs/crlf/file1.yaml create mode 100644 test/clang-apply-replacements/Inputs/format/no.cpp create mode 100644 test/clang-apply-replacements/Inputs/format/no.yaml create mode 100644 test/clang-apply-replacements/Inputs/format/yes.cpp create mode 100644 test/clang-apply-replacements/Inputs/format/yes.yaml create mode 100644 test/clang-apply-replacements/basic.cpp create mode 100644 test/clang-apply-replacements/conflict.cpp create mode 100644 test/clang-apply-replacements/crlf.cpp create mode 100644 test/clang-apply-replacements/format.cpp create mode 100644 test/clang-move/Inputs/database_template.json create mode 100644 test/clang-move/Inputs/enum.h create mode 100644 test/clang-move/Inputs/function_test.cpp create mode 100644 test/clang-move/Inputs/function_test.h create mode 100644 test/clang-move/Inputs/helper_decls_test.cpp create mode 100644 test/clang-move/Inputs/helper_decls_test.h create mode 100644 test/clang-move/Inputs/multiple_class_test.cpp create mode 100644 test/clang-move/Inputs/multiple_class_test.h create mode 100644 test/clang-move/Inputs/template_class_test.cpp create mode 100644 test/clang-move/Inputs/template_class_test.h create mode 100644 test/clang-move/Inputs/test.cpp create mode 100644 test/clang-move/Inputs/test.h create mode 100644 test/clang-move/Inputs/type_alias.h create mode 100644 test/clang-move/move-class.cpp create mode 100644 test/clang-move/move-enum-decl.cpp create mode 100644 test/clang-move/move-function.cpp create mode 100644 test/clang-move/move-multiple-classes.cpp create mode 100644 test/clang-move/move-template-class.cpp create mode 100644 test/clang-move/move-type-alias.cpp create mode 100644 test/clang-move/move-used-helper-decls.cpp create mode 100644 test/clang-query/Inputs/foo.script create mode 100644 test/clang-query/errors.c create mode 100644 test/clang-query/function-decl.c create mode 100644 test/clang-rename/ClassAsTemplateArgument.cpp create mode 100644 test/clang-rename/ClassFindByName.cpp create mode 100644 test/clang-rename/ClassReplacements.cpp create mode 100644 test/clang-rename/ClassSimpleRenaming.cpp create mode 100644 test/clang-rename/ClassTestMulti.cpp create mode 100644 test/clang-rename/ClassTestMultiByName.cpp create mode 100644 test/clang-rename/ComplexFunctionOverride.cpp create mode 100644 test/clang-rename/ComplicatedClassType.cpp create mode 100644 test/clang-rename/Ctor.cpp create mode 100644 test/clang-rename/CtorInitializer.cpp create mode 100644 test/clang-rename/DeclRefExpr.cpp create mode 100644 test/clang-rename/Field.cpp create mode 100644 test/clang-rename/FunctionMacro.cpp create mode 100644 test/clang-rename/FunctionOverride.cpp create mode 100644 test/clang-rename/FunctionWithClassFindByName.cpp create mode 100644 test/clang-rename/IncludeHeaderWithSymbol.cpp create mode 100644 test/clang-rename/Inputs/HeaderWithSymbol.h create mode 100644 test/clang-rename/Inputs/OffsetToNewName.yaml create mode 100644 test/clang-rename/Inputs/QualifiedNameToNewName.yaml create mode 100644 test/clang-rename/InvalidNewName.cpp create mode 100644 test/clang-rename/InvalidOffset.cpp create mode 100644 test/clang-rename/MemberExprMacro.cpp create mode 100644 test/clang-rename/Namespace.cpp create mode 100644 test/clang-rename/NoNewName.cpp create mode 100644 test/clang-rename/TemplateClassInstantiation.cpp create mode 100644 test/clang-rename/TemplateTypename.cpp create mode 100644 test/clang-rename/TemplatedClassFunction.cpp create mode 100644 test/clang-rename/UserDefinedConversion.cpp create mode 100644 test/clang-rename/Variable.cpp create mode 100644 test/clang-rename/VariableMacro.cpp create mode 100644 test/clang-rename/YAMLInput.cpp create mode 100644 test/clang-reorder-fields/AggregatePartialInitialization.cpp create mode 100644 test/clang-reorder-fields/CStructAmbiguousName.cpp create mode 100644 test/clang-reorder-fields/CStructFieldsOrder.cpp create mode 100644 test/clang-reorder-fields/ClassDifferentFieldsAccesses.cpp create mode 100644 test/clang-reorder-fields/ClassMixedInitialization.cpp create mode 100644 test/clang-reorder-fields/ClassSimpleCtor.cpp create mode 100644 test/clang-tidy/Inputs/Headers/a.h create mode 100644 test/clang-tidy/Inputs/Headers/b.h create mode 100644 test/clang-tidy/Inputs/Headers/clang-c/c.h create mode 100644 test/clang-tidy/Inputs/Headers/clang/b.h create mode 100644 test/clang-tidy/Inputs/Headers/cross-file-a.h create mode 100644 test/clang-tidy/Inputs/Headers/cross-file-b.h create mode 100644 test/clang-tidy/Inputs/Headers/cross-file-c.h create mode 100644 test/clang-tidy/Inputs/Headers/gtest/foo.h create mode 100644 test/clang-tidy/Inputs/Headers/i.h create mode 100644 test/clang-tidy/Inputs/Headers/j.h create mode 100644 test/clang-tidy/Inputs/Headers/llvm-c/d.h create mode 100644 test/clang-tidy/Inputs/Headers/llvm/a.h create mode 100644 test/clang-tidy/Inputs/Headers/s.h create mode 100644 test/clang-tidy/Inputs/compilation-database/template.json create mode 100644 test/clang-tidy/Inputs/config-files/.clang-tidy create mode 100644 test/clang-tidy/Inputs/config-files/1/.clang-tidy create mode 100644 test/clang-tidy/Inputs/explain-config/.clang-tidy create mode 100644 test/clang-tidy/Inputs/file-filter/header1.h create mode 100644 test/clang-tidy/Inputs/file-filter/header2.h create mode 100644 test/clang-tidy/Inputs/file-filter/system/system-header.h create mode 100644 test/clang-tidy/Inputs/google-namespaces.h create mode 100644 test/clang-tidy/Inputs/line-filter/header1.h create mode 100644 test/clang-tidy/Inputs/line-filter/header2.h create mode 100644 test/clang-tidy/Inputs/line-filter/header3.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/assert.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/complex.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/ctype.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/errno.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/fenv.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/float.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/inttypes.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/iso646.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/limits.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/locale.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/math.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/setjmp.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/signal.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stdalign.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stdarg.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stdbool.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stddef.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stdint.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stdio.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/stdlib.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/string.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/tgmath.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/time.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/uchar.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/wchar.h create mode 100644 test/clang-tidy/Inputs/modernize-deprecated-headers/wctype.h create mode 100644 test/clang-tidy/Inputs/modernize-loop-convert/structures.h create mode 100644 test/clang-tidy/Inputs/modernize-pass-by-value/header-with-fix.h create mode 100644 test/clang-tidy/Inputs/modernize-pass-by-value/header.h create mode 100644 test/clang-tidy/Inputs/modernize-replace-auto-ptr/memory.h create mode 100644 test/clang-tidy/Inputs/modernize-use-auto/containers.h create mode 100644 test/clang-tidy/Inputs/mpi-type-mismatch/mpimock.h create mode 100644 test/clang-tidy/Inputs/nolint/trigger_warning.h create mode 100644 test/clang-tidy/Inputs/overlapping/o.h create mode 100644 test/clang-tidy/Inputs/readability-identifier-naming/system/system-header.h create mode 100644 test/clang-tidy/Inputs/readability-identifier-naming/user-header.h create mode 100644 test/clang-tidy/Inputs/unused-using-decls.h create mode 100644 test/clang-tidy/basic.cpp create mode 100644 test/clang-tidy/boost-use-to-string.cpp create mode 100644 test/clang-tidy/cert-env33-c.c create mode 100644 test/clang-tidy/cert-err34-c.c create mode 100644 test/clang-tidy/cert-err34-c.cpp create mode 100644 test/clang-tidy/cert-flp30-c.c create mode 100644 test/clang-tidy/cert-limited-randomness.c create mode 100644 test/clang-tidy/cert-limited-randomness.cpp create mode 100644 test/clang-tidy/cert-oop11-cpp.cpp create mode 100644 test/clang-tidy/cert-setlongjmp.cpp create mode 100644 test/clang-tidy/cert-static-object-exception.cpp create mode 100644 test/clang-tidy/cert-throw-exception-type.cpp create mode 100644 test/clang-tidy/cert-variadic-function-def.cpp create mode 100755 test/clang-tidy/check_clang_tidy.py create mode 100644 test/clang-tidy/clang-tidy-diff.cpp create mode 100644 test/clang-tidy/clang-tidy-run-with-database.cpp create mode 100644 test/clang-tidy/clean-up-code.cpp create mode 100644 test/clang-tidy/config-files.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-interfaces-global-init.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-no-malloc.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-bounds-array-to-pointer-decay.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-c++03.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index-gslheader.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-bounds-constant-array-index.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-bounds-pointer-arithmetic.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-const-cast.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-cstyle-cast.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-member-init-cxx98.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-member-init-delayed.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-member-init.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-reinterpret-cast.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-static-cast-downcast.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-union-access.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-pro-type-vararg.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-slicing.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-special-member-functions-cxx-03.cpp create mode 100644 test/clang-tidy/cppcoreguidelines-special-member-functions.cpp create mode 100644 test/clang-tidy/custom-diagnostics.cpp create mode 100644 test/clang-tidy/deduplication.cpp create mode 100644 test/clang-tidy/diagnostic.cpp create mode 100644 test/clang-tidy/explain-checks.cpp create mode 100644 test/clang-tidy/extra-args.cpp create mode 100644 test/clang-tidy/file-filter.cpp create mode 100644 test/clang-tidy/fix-errors.cpp create mode 100644 test/clang-tidy/fix.cpp create mode 100644 test/clang-tidy/google-build-explicit-make-pair.cpp create mode 100644 test/clang-tidy/google-default-arguments.cpp create mode 100644 test/clang-tidy/google-explicit-constructor.cpp create mode 100644 test/clang-tidy/google-module.cpp create mode 100644 test/clang-tidy/google-namespaces.cpp create mode 100644 test/clang-tidy/google-overloaded-unary-and.cpp create mode 100644 test/clang-tidy/google-readability-casting.c create mode 100644 test/clang-tidy/google-readability-casting.cpp create mode 100644 test/clang-tidy/google-readability-namespace-comments.cpp create mode 100644 test/clang-tidy/google-readability-todo.cpp create mode 100644 test/clang-tidy/google-runtime-int-std.cpp create mode 100644 test/clang-tidy/google-runtime-int.c create mode 100644 test/clang-tidy/google-runtime-int.cpp create mode 100644 test/clang-tidy/google-runtime-member-string-references.cpp create mode 100644 test/clang-tidy/google-runtime-memset-zero-length.cpp create mode 100644 test/clang-tidy/google-runtime-references.cpp create mode 100644 test/clang-tidy/line-filter.cpp create mode 100644 test/clang-tidy/list-checks.cpp create mode 100644 test/clang-tidy/llvm-include-order.cpp create mode 100644 test/clang-tidy/llvm-twine-local.cpp create mode 100644 test/clang-tidy/macros.cpp create mode 100644 test/clang-tidy/misc-argument-comment-strict.cpp create mode 100644 test/clang-tidy/misc-argument-comment.cpp create mode 100644 test/clang-tidy/misc-assert-side-effect.cpp create mode 100644 test/clang-tidy/misc-bool-pointer-implicit-conversion.cpp create mode 100644 test/clang-tidy/misc-dangling-handle.cpp create mode 100644 test/clang-tidy/misc-definitions-in-headers.hpp create mode 100644 test/clang-tidy/misc-fold-init-type.cpp create mode 100644 test/clang-tidy/misc-forward-declaration-namespace.cpp create mode 100644 test/clang-tidy/misc-inaccurate-erase.cpp create mode 100644 test/clang-tidy/misc-incorrect-roundings.cpp create mode 100644 test/clang-tidy/misc-inefficient-algorithm.cpp create mode 100644 test/clang-tidy/misc-macro-parentheses-cmdline.cpp create mode 100644 test/clang-tidy/misc-macro-parentheses.cpp create mode 100644 test/clang-tidy/misc-macro-repeated-side-effects.c create mode 100644 test/clang-tidy/misc-misplaced-const.c create mode 100644 test/clang-tidy/misc-misplaced-const.cpp create mode 100644 test/clang-tidy/misc-misplaced-widening-cast-explicit-only.cpp create mode 100644 test/clang-tidy/misc-misplaced-widening-cast.cpp create mode 100644 test/clang-tidy/misc-move-const-arg.cpp create mode 100644 test/clang-tidy/misc-move-constructor-init.cpp create mode 100644 test/clang-tidy/misc-move-forwarding-reference.cpp create mode 100644 test/clang-tidy/misc-multiple-statement-macro.cpp create mode 100644 test/clang-tidy/misc-new-delete-overloads-sized-dealloc.cpp create mode 100644 test/clang-tidy/misc-new-delete-overloads.cpp create mode 100644 test/clang-tidy/misc-noexcept-move-constructor.cpp create mode 100644 test/clang-tidy/misc-non-copyable-objects.c create mode 100644 test/clang-tidy/misc-non-copyable-objects.cpp create mode 100644 test/clang-tidy/misc-redundant-expression.cpp create mode 100644 test/clang-tidy/misc-sizeof-container.cpp create mode 100644 test/clang-tidy/misc-sizeof-expression.cpp create mode 100644 test/clang-tidy/misc-static-assert.c create mode 100644 test/clang-tidy/misc-static-assert.cpp create mode 100644 test/clang-tidy/misc-string-compare.cpp create mode 100644 test/clang-tidy/misc-string-constructor.cpp create mode 100644 test/clang-tidy/misc-string-integer-assignment.cpp create mode 100644 test/clang-tidy/misc-string-literal-with-embedded-nul.cpp create mode 100644 test/clang-tidy/misc-suspicious-enum-usage-strict.cpp create mode 100644 test/clang-tidy/misc-suspicious-enum-usage.cpp create mode 100644 test/clang-tidy/misc-suspicious-missing-comma.cpp create mode 100644 test/clang-tidy/misc-suspicious-semicolon-fail.cpp create mode 100644 test/clang-tidy/misc-suspicious-semicolon.cpp create mode 100644 test/clang-tidy/misc-suspicious-string-compare.c create mode 100644 test/clang-tidy/misc-suspicious-string-compare.cpp create mode 100644 test/clang-tidy/misc-swapped-arguments.cpp create mode 100644 test/clang-tidy/misc-throw-by-value-catch-by-reference.cpp create mode 100644 test/clang-tidy/misc-unconventional-assign-operator.cpp create mode 100644 test/clang-tidy/misc-undelegated-constructor-cxx98.cpp create mode 100644 test/clang-tidy/misc-undelegated-constructor.cpp create mode 100644 test/clang-tidy/misc-uniqueptr-reset-release.cpp create mode 100644 test/clang-tidy/misc-unused-alias-decls.cpp create mode 100644 test/clang-tidy/misc-unused-parameters.c create mode 100644 test/clang-tidy/misc-unused-parameters.cpp create mode 100644 test/clang-tidy/misc-unused-raii.cpp create mode 100644 test/clang-tidy/misc-unused-using-decls.cpp create mode 100644 test/clang-tidy/misc-use-after-move.cpp create mode 100644 test/clang-tidy/misc-virtual-near-miss.cpp create mode 100644 test/clang-tidy/modernize-avoid-bind.cpp create mode 100644 test/clang-tidy/modernize-deprecated-headers-cxx03.cpp create mode 100644 test/clang-tidy/modernize-deprecated-headers-cxx11.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-assert-failure.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-basic.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-camelback.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-const.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-extra.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-lowercase.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-negative.cpp create mode 100644 test/clang-tidy/modernize-loop-convert-uppercase.cpp create mode 100644 test/clang-tidy/modernize-loop-convert.c create mode 100644 test/clang-tidy/modernize-make-shared.cpp create mode 100644 test/clang-tidy/modernize-make-unique.cpp create mode 100644 test/clang-tidy/modernize-pass-by-value-header.cpp create mode 100644 test/clang-tidy/modernize-pass-by-value-macro-header.cpp create mode 100644 test/clang-tidy/modernize-pass-by-value-multi-fixes.cpp create mode 100644 test/clang-tidy/modernize-pass-by-value.cpp create mode 100644 test/clang-tidy/modernize-raw-string-literal-delimiter.cpp create mode 100644 test/clang-tidy/modernize-raw-string-literal.cpp create mode 100644 test/clang-tidy/modernize-redundant-void-arg-delayed.cpp create mode 100644 test/clang-tidy/modernize-redundant-void-arg.c create mode 100644 test/clang-tidy/modernize-redundant-void-arg.cpp create mode 100644 test/clang-tidy/modernize-replace-auto-ptr.cpp create mode 100644 test/clang-tidy/modernize-shrink-to-fit.cpp create mode 100644 test/clang-tidy/modernize-use-auto-cast-remove-stars.cpp create mode 100644 test/clang-tidy/modernize-use-auto-cast.cpp create mode 100644 test/clang-tidy/modernize-use-auto-iterator.cpp create mode 100644 test/clang-tidy/modernize-use-auto-new-remove-stars.cpp create mode 100644 test/clang-tidy/modernize-use-auto-new.cpp create mode 100644 test/clang-tidy/modernize-use-bool-literals.cpp create mode 100644 test/clang-tidy/modernize-use-default-member-init-assignment.cpp create mode 100644 test/clang-tidy/modernize-use-default-member-init.cpp create mode 100644 test/clang-tidy/modernize-use-emplace.cpp create mode 100644 test/clang-tidy/modernize-use-equals-default-copy.cpp create mode 100644 test/clang-tidy/modernize-use-equals-default-delayed.cpp create mode 100644 test/clang-tidy/modernize-use-equals-default.cpp create mode 100644 test/clang-tidy/modernize-use-equals-delete.cpp create mode 100644 test/clang-tidy/modernize-use-nullptr-basic.cpp create mode 100644 test/clang-tidy/modernize-use-nullptr.c create mode 100644 test/clang-tidy/modernize-use-nullptr.cpp create mode 100644 test/clang-tidy/modernize-use-override-cxx98.cpp create mode 100644 test/clang-tidy/modernize-use-override-ms.cpp create mode 100644 test/clang-tidy/modernize-use-override.cpp create mode 100644 test/clang-tidy/modernize-use-transparent-functors.cpp create mode 100644 test/clang-tidy/modernize-use-using.cpp create mode 100644 test/clang-tidy/mpi-buffer-deref.cpp create mode 100644 test/clang-tidy/mpi-type-mismatch.cpp create mode 100644 test/clang-tidy/nolint.cpp create mode 100644 test/clang-tidy/overlapping.cpp create mode 100644 test/clang-tidy/performance-faster-string-find.cpp create mode 100644 test/clang-tidy/performance-for-range-copy-warn-on-all-auto-copies.cpp create mode 100644 test/clang-tidy/performance-for-range-copy.cpp create mode 100644 test/clang-tidy/performance-implicit-cast-in-loop.cpp create mode 100644 test/clang-tidy/performance-inefficient-string-concatenation.cpp create mode 100644 test/clang-tidy/performance-type-promotion-in-math-fn.cpp create mode 100644 test/clang-tidy/performance-unnecessary-copy-initialization.cpp create mode 100644 test/clang-tidy/performance-unnecessary-value-param-delayed.cpp create mode 100644 test/clang-tidy/performance-unnecessary-value-param-incomplete-type.cpp create mode 100644 test/clang-tidy/performance-unnecessary-value-param.cpp create mode 100644 test/clang-tidy/readability-avoid-const-params-in-decls.cpp create mode 100644 test/clang-tidy/readability-braces-around-statements-assert-failure.cpp create mode 100644 test/clang-tidy/readability-braces-around-statements-few-lines.cpp create mode 100644 test/clang-tidy/readability-braces-around-statements-same-line.cpp create mode 100644 test/clang-tidy/readability-braces-around-statements-single-line.cpp create mode 100644 test/clang-tidy/readability-braces-around-statements.cpp create mode 100644 test/clang-tidy/readability-container-size-empty.cpp create mode 100644 test/clang-tidy/readability-delete-null-pointer.cpp create mode 100644 test/clang-tidy/readability-deleted-default.cpp create mode 100644 test/clang-tidy/readability-else-after-return.cpp create mode 100644 test/clang-tidy/readability-function-size.cpp create mode 100644 test/clang-tidy/readability-identifier-naming.cpp create mode 100644 test/clang-tidy/readability-implicit-bool-cast-allow-conditional-casts.cpp create mode 100644 test/clang-tidy/readability-implicit-bool-cast-cxx98.cpp create mode 100644 test/clang-tidy/readability-implicit-bool-cast.cpp create mode 100644 test/clang-tidy/readability-inconsistent-declaration-parameter-name.cpp create mode 100644 test/clang-tidy/readability-misplaced-array-index.cpp create mode 100644 test/clang-tidy/readability-named-parameter.cpp create mode 100644 test/clang-tidy/readability-non-const-parameter.cpp create mode 100644 test/clang-tidy/readability-redundant-control-flow.cpp create mode 100644 test/clang-tidy/readability-redundant-declaration.cpp create mode 100644 test/clang-tidy/readability-redundant-function-ptr-dereference.cpp create mode 100644 test/clang-tidy/readability-redundant-member-init.cpp create mode 100644 test/clang-tidy/readability-redundant-smartptr-get.cpp create mode 100644 test/clang-tidy/readability-redundant-string-cstr-msvc.cpp create mode 100644 test/clang-tidy/readability-redundant-string-cstr.cpp create mode 100644 test/clang-tidy/readability-redundant-string-init-msvc.cpp create mode 100644 test/clang-tidy/readability-redundant-string-init.cpp create mode 100644 test/clang-tidy/readability-simplify-bool-expr-chained-conditional-assignment.cpp create mode 100644 test/clang-tidy/readability-simplify-bool-expr-chained-conditional-return.cpp create mode 100644 test/clang-tidy/readability-simplify-bool-expr.cpp create mode 100644 test/clang-tidy/readability-static-definition-in-anonymous-namespace.cpp create mode 100644 test/clang-tidy/readability-uniqueptr-delete-release.cpp create mode 100644 test/clang-tidy/select-checks.cpp create mode 100644 test/clang-tidy/serialize-diagnostics.cpp create mode 100644 test/clang-tidy/static-analyzer-config.cpp create mode 100644 test/clang-tidy/static-analyzer.cpp create mode 100644 test/clang-tidy/temporaries.cpp create mode 100644 test/clang-tidy/validate-check-names.cpp create mode 100644 test/clang-tidy/werrors-diagnostics.cpp create mode 100644 test/clang-tidy/werrors-plural.cpp create mode 100644 test/clang-tidy/werrors.cpp create mode 100644 test/include-fixer/Inputs/database_template.json create mode 100644 test/include-fixer/Inputs/fake_yaml_db.yaml create mode 100644 test/include-fixer/Inputs/merge/a.yaml create mode 100644 test/include-fixer/Inputs/merge/b.yaml create mode 100644 test/include-fixer/commandline_options.cpp create mode 100644 test/include-fixer/exit_on_fatal.cpp create mode 100644 test/include-fixer/fixeddb.cpp create mode 100644 test/include-fixer/include_path.cpp create mode 100644 test/include-fixer/merge.test create mode 100644 test/include-fixer/multiple_fixes.cpp create mode 100644 test/include-fixer/prefix_variable.cpp create mode 100644 test/include-fixer/query_symbol.cpp create mode 100644 test/include-fixer/ranking.cpp create mode 100644 test/include-fixer/yamldb.cpp create mode 100644 test/include-fixer/yamldb_autodetect.cpp create mode 100644 test/include-fixer/yamldb_plugin.cpp create mode 100644 test/lit.cfg create mode 100644 test/lit.site.cfg.in create mode 100644 test/modularize/Inputs/Anonymous.h create mode 100644 test/modularize/Inputs/CompileError/HasError.h create mode 100644 test/modularize/Inputs/CompileError/Level1A.h create mode 100644 test/modularize/Inputs/CompileError/module.modulemap create mode 100644 test/modularize/Inputs/CoverageNoProblems/Includes1/.hidden/DontFindMe.h create mode 100644 test/modularize/Inputs/CoverageNoProblems/Includes1/Level1A.h create mode 100644 test/modularize/Inputs/CoverageNoProblems/Includes2/Level2A.h create mode 100644 test/modularize/Inputs/CoverageNoProblems/NonIncludes/Level3A.h create mode 100644 test/modularize/Inputs/CoverageNoProblems/module.modulemap create mode 100644 test/modularize/Inputs/CoverageProblems/Level1A.h create mode 100644 test/modularize/Inputs/CoverageProblems/Level1B.h create mode 100644 test/modularize/Inputs/CoverageProblems/Level2A.h create mode 100644 test/modularize/Inputs/CoverageProblems/Level2B.h create mode 100644 test/modularize/Inputs/CoverageProblems/Level3A.h create mode 100644 test/modularize/Inputs/CoverageProblems/Level3B create mode 100644 test/modularize/Inputs/CoverageProblems/Sub/Level3B.h create mode 100644 test/modularize/Inputs/CoverageProblems/UmbrellaFile.h create mode 100644 test/modularize/Inputs/CoverageProblems/UmbrellaInclude1.h create mode 100644 test/modularize/Inputs/CoverageProblems/UmbrellaInclude2.h create mode 100644 test/modularize/Inputs/CoverageProblems/UmbrellaSub/Umbrell1.h create mode 100644 test/modularize/Inputs/CoverageProblems/UmbrellaSub/Umbrell2.h create mode 100644 test/modularize/Inputs/CoverageProblems/module.modulemap create mode 100644 test/modularize/Inputs/DuplicateHeader1.h create mode 100644 test/modularize/Inputs/DuplicateHeader2.h create mode 100644 test/modularize/Inputs/Empty.h create mode 100644 test/modularize/Inputs/HeaderGuard.h create mode 100644 test/modularize/Inputs/HeaderGuardSub1.h create mode 100644 test/modularize/Inputs/HeaderGuardSub2.h create mode 100644 test/modularize/Inputs/HeaderGuardSubSub.h create mode 100644 test/modularize/Inputs/HeaderGuardSubSubDefined.h create mode 100644 test/modularize/Inputs/IncludeInExtern.h create mode 100644 test/modularize/Inputs/IncludeInNamespace.h create mode 100644 test/modularize/Inputs/InconsistentHeader1.h create mode 100644 test/modularize/Inputs/InconsistentHeader2.h create mode 100644 test/modularize/Inputs/InconsistentSubHeader.h create mode 100644 test/modularize/Inputs/IsDependent.h create mode 100644 test/modularize/Inputs/MissingHeader/Level1A.h create mode 100644 test/modularize/Inputs/MissingHeader/module.modulemap create mode 100644 test/modularize/Inputs/NamespaceClasses.h create mode 100644 test/modularize/Inputs/NestedMacro.h create mode 100644 test/modularize/Inputs/NoProblems.modulemap create mode 100644 test/modularize/Inputs/ProblemsDuplicate.modulemap create mode 100644 test/modularize/Inputs/SomeDecls.h create mode 100644 test/modularize/Inputs/SomeOtherTypes.h create mode 100644 test/modularize/Inputs/SomeTypes.h create mode 100644 test/modularize/Inputs/SubModule1/Header1.h create mode 100644 test/modularize/Inputs/SubModule1/Header2.h create mode 100644 test/modularize/Inputs/SubModule2/Header3.h create mode 100644 test/modularize/Inputs/SubModule2/Header4.h create mode 100644 test/modularize/Inputs/TemplateClasses.h create mode 100644 test/modularize/NoProblems.modularize create mode 100644 test/modularize/NoProblemsAnonymous.modularize create mode 100644 test/modularize/NoProblemsAssistant.modularize create mode 100644 test/modularize/NoProblemsCoverage.modularize create mode 100644 test/modularize/NoProblemsDependencies.modularize create mode 100644 test/modularize/NoProblemsGuard.modularize create mode 100644 test/modularize/NoProblemsList.modularize create mode 100644 test/modularize/NoProblemsNamespace.modularize create mode 100644 test/modularize/NoProblemsNamespaceClasses.modularize create mode 100644 test/modularize/NoProblemsNestedMacro.modularize create mode 100644 test/modularize/NoProblemsTemplateClasses.modularize create mode 100644 test/modularize/ProblemsCompileError.modularize create mode 100644 test/modularize/ProblemsCoverage.modularize create mode 100644 test/modularize/ProblemsDisplayLists.modularize create mode 100644 test/modularize/ProblemsDuplicate.modularize create mode 100644 test/modularize/ProblemsExternC.modularize create mode 100644 test/modularize/ProblemsInconsistent.modularize create mode 100644 test/modularize/ProblemsMissingHeader.modularize create mode 100644 test/modularize/ProblemsNamespace.modularize create mode 100644 test/modularize/SubModule2.h create mode 100644 test/pp-trace/Inputs/Level1A.h create mode 100644 test/pp-trace/Inputs/Level1B.h create mode 100644 test/pp-trace/Inputs/Level2A.h create mode 100644 test/pp-trace/Inputs/Level2B.h create mode 100644 test/pp-trace/Inputs/ModularizeList.txt create mode 100644 test/pp-trace/Inputs/module.map create mode 100644 test/pp-trace/pp-trace-conditional.cpp create mode 100644 test/pp-trace/pp-trace-ident.cpp create mode 100644 test/pp-trace/pp-trace-include.cpp create mode 100644 test/pp-trace/pp-trace-macro.cpp create mode 100644 test/pp-trace/pp-trace-modules.cpp create mode 100644 test/pp-trace/pp-trace-pragma-general.cpp create mode 100644 test/pp-trace/pp-trace-pragma-ms.cpp create mode 100644 test/pp-trace/pp-trace-pragma-opencl.cpp create mode 100644 tool-template/CMakeLists.txt create mode 100644 tool-template/ToolTemplate.cpp create mode 100644 unittests/CMakeLists.txt create mode 100644 unittests/change-namespace/CMakeLists.txt create mode 100644 unittests/change-namespace/ChangeNamespaceTests.cpp create mode 100644 unittests/clang-apply-replacements/CMakeLists.txt create mode 100644 unittests/clang-apply-replacements/ReformattingTest.cpp create mode 100644 unittests/clang-move/CMakeLists.txt create mode 100644 unittests/clang-move/ClangMoveTests.cpp create mode 100644 unittests/clang-query/CMakeLists.txt create mode 100644 unittests/clang-query/QueryEngineTest.cpp create mode 100644 unittests/clang-query/QueryParserTest.cpp create mode 100644 unittests/clang-tidy/CMakeLists.txt create mode 100644 unittests/clang-tidy/ClangTidyDiagnosticConsumerTest.cpp create mode 100644 unittests/clang-tidy/ClangTidyOptionsTest.cpp create mode 100644 unittests/clang-tidy/ClangTidyTest.h create mode 100644 unittests/clang-tidy/GoogleModuleTest.cpp create mode 100644 unittests/clang-tidy/IncludeInserterTest.cpp create mode 100644 unittests/clang-tidy/LLVMModuleTest.cpp create mode 100644 unittests/clang-tidy/MiscModuleTest.cpp create mode 100644 unittests/clang-tidy/NamespaceAliaserTest.cpp create mode 100644 unittests/clang-tidy/OverlappingReplacementsTest.cpp create mode 100644 unittests/clang-tidy/ReadabilityModuleTest.cpp create mode 100644 unittests/clang-tidy/UsingInserterTest.cpp create mode 100644 unittests/include-fixer/CMakeLists.txt create mode 100644 unittests/include-fixer/IncludeFixerTest.cpp create mode 100644 unittests/include-fixer/find-all-symbols/CMakeLists.txt create mode 100644 unittests/include-fixer/find-all-symbols/FindAllSymbolsTests.cpp create mode 100644 unittests/include/common/VirtualFileHelper.h diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 000000000..f84658176 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "project_id" : "clang-tools-extra", + "conduit_uri" : "https://reviews.llvm.org/" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ac573c4aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +#==============================================================================# +# This file specifies intentionally untracked files that git should ignore. +# See: http://www.kernel.org/pub/software/scm/git/docs/gitignore.html +# +# This file is intentionally different from the output of `git svn show-ignore`, +# as most of those are useless. +#==============================================================================# + +#==============================================================================# +# File extensions to be ignored anywhere in the tree. +#==============================================================================# +# Temp files created by most text editors. +*~ +# Merge files created by git. +*.orig +# Byte compiled python modules. +*.pyc +# vim swap files +.*.swp +.sw? + +#==============================================================================# +# Explicit files to ignore (only matches one). +#==============================================================================# +cscope.files +cscope.out +.clang_complete + +#==============================================================================# +# Directories to ignore (do not add trailing '/'s, they skip symlinks). +#==============================================================================# +docs/_build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..2f36eaa00 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +add_subdirectory(clang-apply-replacements) +add_subdirectory(clang-rename) +add_subdirectory(clang-reorder-fields) +add_subdirectory(modularize) +if(CLANG_ENABLE_STATIC_ANALYZER) +add_subdirectory(clang-tidy) +add_subdirectory(clang-tidy-vs) +endif() + +add_subdirectory(change-namespace) +add_subdirectory(clang-query) +add_subdirectory(clang-move) +add_subdirectory(include-fixer) +add_subdirectory(pp-trace) +add_subdirectory(tool-template) + +# Add the common testsuite after all the tools. +# TODO: Support tests with more granularity when features are off? +if(CLANG_ENABLE_STATIC_ANALYZER AND CLANG_INCLUDE_TESTS) +add_subdirectory(test) +add_subdirectory(unittests) +endif() + +option(CLANG_TOOLS_EXTRA_INCLUDE_DOCS "Generate build targets for the Clang Extra Tools docs." + ${LLVM_INCLUDE_DOCS}) +if( CLANG_TOOLS_EXTRA_INCLUDE_DOCS ) + add_subdirectory(docs) +endif() + diff --git a/CODE_OWNERS.TXT b/CODE_OWNERS.TXT new file mode 100644 index 000000000..af8beb4a9 --- /dev/null +++ b/CODE_OWNERS.TXT @@ -0,0 +1,21 @@ +This file is a list of the people responsible for ensuring that patches for a +particular tool are reviewed, either by themself or by someone else. They are +also the gatekeepers for their part of Clang, with the final word on what goes +in or not. + +The list is sorted by surname and formatted to allow easy grepping and +beautification by scripts. The fields are: name (N), email (E), web-address +(W), PGP key ID and fingerprint (P), description (D), and snail-mail address +(S). + +N: Peter Collingbourne +E: peter@pcc.me.uk +D: clang-query + +N: Manuel Klimek +E: klimek@google.com +D: clang-rename, all parts of clang-tools-extra not covered by someone else + +N: Alexander Kornienko +E: alexfh@google.com +D: clang-tidy diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 000000000..3f4fa1f12 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,62 @@ +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- +clang-tidy clang-tidy/cert 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..b2f31a9be --- /dev/null +++ b/change-namespace/ChangeNamespace.cpp @@ -0,0 +1,906 @@ +//===-- 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; +} + +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; + llvm::SmallVector PartialNsNameSplitted; + PartialNsName.split(PartialNsNameSplitted, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); + 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. +// \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; + + while (!DeclName.consume_front((NsName + "::").str())) { + const auto Pos = NsName.find_last_of(':'); + if (Pos == llvm::StringRef::npos) + return DeclName; + assert(Pos > 0); + NsName = NsName.substr(0, Pos - 1); + } + return DeclName; +} + +std::string wrapCodeInNamespace(StringRef NestedNs, std::string Code) { + if (Code.back() != '\n') + Code += "\n"; + llvm::SmallVector NsSplitted; + NestedNs.split(NsSplitted, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); + 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->getLocation()); + Loc = SM.getSpellingLoc(Loc); + return SM.isBeforeInTranslationUnit(DeclLoc, Loc) && + (SM.getFileID(DeclLoc) == SM.getFileID(Loc) && + isNestedDeclContext(DeclCtx, D->getDeclContext())); +} + +} // anonymous namespace + +ChangeNamespaceTool::ChangeNamespaceTool( + llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern, + std::map *FileToReplacements, + llvm::StringRef FallbackStyle) + : FallbackStyle(FallbackStyle), FileToReplacements(*FileToReplacements), + OldNamespace(OldNs.ltrim(':')), NewNamespace(NewNs.ltrim(':')), + FilePattern(FilePattern), FilePatternRE(FilePattern) { + FileToReplacements->clear(); + llvm::SmallVector OldNsSplitted; + llvm::SmallVector NewNsSplitted; + llvm::StringRef(OldNamespace).split(OldNsSplitted, "::"); + llvm::StringRef(NewNamespace).split(NewNsSplitted, "::"); + // 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); +} + +void ChangeNamespaceTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + std::string FullOldNs = "::" + OldNamespace; + // Prefix is the outer-most namespace in DiffOldNamespace. For example, if the + // OldNamespace is "a::b::c" and DiffOldNamespace is "b::c", then Prefix will + // be "a::b". Declarations in this namespace will not be visible in the new + // namespace. If DiffOldNamespace is empty, Prefix will be a invalid name "-". + llvm::SmallVector DiffOldNsSplitted; + llvm::StringRef(DiffOldNamespace) + .split(DiffOldNsSplitted, "::", /*MaxSplit=*/-1, + /*KeepEmpty=*/false); + std::string Prefix = "-"; + if (!DiffOldNsSplitted.empty()) + Prefix = (StringRef(FullOldNs).drop_back(DiffOldNamespace.size()) + + DiffOldNsSplitted.front()) + .str(); + auto IsInMovedNs = + allOf(hasAncestor(namespaceDecl(hasName(FullOldNs)).bind("ns_decl")), + isExpansionInFileMatching(FilePattern)); + auto IsVisibleInNewNs = anyOf( + IsInMovedNs, unless(hasAncestor(namespaceDecl(hasName(Prefix))))); + // Match using declarations. + Finder->addMatcher( + usingDecl(isExpansionInFileMatching(FilePattern), IsVisibleInNewNs) + .bind("using"), + this); + // Match using namespace declarations. + Finder->addMatcher(usingDirectiveDecl(isExpansionInFileMatching(FilePattern), + IsVisibleInNewNs) + .bind("using_namespace"), + this); + // Match namespace alias declarations. + Finder->addMatcher(namespaceAliasDecl(isExpansionInFileMatching(FilePattern), + IsVisibleInNewNs) + .bind("namespace_alias"), + this); + + // Match old namespace blocks. + Finder->addMatcher( + namespaceDecl(hasName(FullOldNs), isExpansionInFileMatching(FilePattern)) + .bind("old_ns"), + this); + + // Match class forward-declarations in the old namespace. + // Note that forward-declarations in classes are not matched. + Finder->addMatcher(cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition())), + IsInMovedNs, hasParent(namespaceDecl())) + .bind("class_fwd_decl"), + this); + + // Match template class forward-declarations in the old namespace. + Finder->addMatcher( + classTemplateDecl(unless(hasDescendant(cxxRecordDecl(isDefinition()))), + IsInMovedNs, hasParent(namespaceDecl())) + .bind("template_class_fwd_decl"), + this); + + // Match references to types that are not defined in the old namespace. + // Forward-declarations in the old namespace are also matched since they will + // be moved back to the old namespace. + auto DeclMatcher = namedDecl( + hasAncestor(namespaceDecl()), + unless(anyOf( + isImplicit(), hasAncestor(namespaceDecl(isAnonymous())), + hasAncestor(cxxRecordDecl()), + allOf(IsInMovedNs, unless(cxxRecordDecl(unless(isDefinition()))))))); + + // Using shadow declarations in classes always refers to base class, which + // does not need to be qualified since it can be inferred from inheritance. + // Note that this does not match using alias declarations. + auto UsingShadowDeclInClass = + usingDecl(hasAnyUsingShadowDecl(decl()), hasParent(cxxRecordDecl())); + + // Match TypeLocs on the declaration. Carefully match only the outermost + // TypeLoc and template specialization arguments (which are not outermost) + // that are directly linked to types matching `DeclMatcher`. Nested name + // specifier locs are handled separately below. + Finder->addMatcher( + typeLoc(IsInMovedNs, + loc(qualType(hasDeclaration(DeclMatcher.bind("from_decl")))), + unless(anyOf(hasParent(typeLoc(loc(qualType( + allOf(hasDeclaration(DeclMatcher), + unless(templateSpecializationType())))))), + hasParent(nestedNameSpecifierLoc()), + hasAncestor(isImplicit()), + hasAncestor(UsingShadowDeclInClass))), + hasAncestor(decl().bind("dc"))) + .bind("type"), + this); + + // Types in `UsingShadowDecl` is not matched by `typeLoc` above, so we need to + // special case it. + // Since using declarations inside classes must have the base class in the + // nested name specifier, we leave it to the nested name specifier matcher. + Finder->addMatcher(usingDecl(IsInMovedNs, hasAnyUsingShadowDecl(decl()), + unless(UsingShadowDeclInClass)) + .bind("using_with_shadow"), + this); + + // Handle types in nested name specifier. Specifiers that are in a TypeLoc + // matched above are not matched, e.g. "A::" in "A::A" is not matched since + // "A::A" would have already been fixed. + Finder->addMatcher( + nestedNameSpecifierLoc( + hasAncestor(decl(IsInMovedNs).bind("dc")), + loc(nestedNameSpecifier( + specifiesType(hasDeclaration(DeclMatcher.bind("from_decl"))))), + unless(anyOf(hasAncestor(isImplicit()), + hasAncestor(UsingShadowDeclInClass), + hasAncestor(typeLoc(loc(qualType(hasDeclaration( + decl(equalsBoundNode("from_decl")))))))))) + .bind("nested_specifier_loc"), + this); + + // Matches base class initializers in constructors. TypeLocs of base class + // initializers do not need to be fixed. For example, + // class X : public a::b::Y { + // public: + // X() : Y::Y() {} // Y::Y do not need namespace specifier. + // }; + Finder->addMatcher( + cxxCtorInitializer(isBaseInitializer()).bind("base_initializer"), this); + + // Handle function. + // Only handle functions that are defined in a namespace excluding member + // function, static methods (qualified by nested specifier), and functions + // defined in the global namespace. + // Note that the matcher does not exclude calls to out-of-line static method + // definitions, so we need to exclude them in the callback handler. + auto FuncMatcher = + functionDecl(unless(anyOf(cxxMethodDecl(), IsInMovedNs, + hasAncestor(namespaceDecl(isAnonymous())), + hasAncestor(cxxRecordDecl()))), + hasParent(namespaceDecl())); + Finder->addMatcher(decl(forEachDescendant(expr(anyOf( + callExpr(callee(FuncMatcher)).bind("call"), + declRefExpr(to(FuncMatcher.bind("func_decl"))) + .bind("func_ref")))), + IsInMovedNs, unless(isImplicit())) + .bind("dc"), + this); + + auto GlobalVarMatcher = varDecl( + hasGlobalStorage(), hasParent(namespaceDecl()), + unless(anyOf(IsInMovedNs, hasAncestor(namespaceDecl(isAnonymous()))))); + Finder->addMatcher(declRefExpr(IsInMovedNs, hasAncestor(decl().bind("dc")), + to(GlobalVarMatcher.bind("var_decl"))) + .bind("var_ref"), + this); +} + +void ChangeNamespaceTool::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const auto *Using = Result.Nodes.getNodeAs("using")) { + UsingDecls.insert(Using); + } else if (const auto *UsingNamespace = + Result.Nodes.getNodeAs( + "using_namespace")) { + UsingNamespaceDecls.insert(UsingNamespace); + } else if (const auto *NamespaceAlias = + Result.Nodes.getNodeAs( + "namespace_alias")) { + NamespaceAliasDecls.insert(NamespaceAlias); + } else if (const auto *NsDecl = + Result.Nodes.getNodeAs("old_ns")) { + moveOldNamespace(Result, NsDecl); + } else if (const auto *FwdDecl = + Result.Nodes.getNodeAs("class_fwd_decl")) { + moveClassForwardDeclaration(Result, cast(FwdDecl)); + } else if (const auto *TemplateFwdDecl = + Result.Nodes.getNodeAs( + "template_class_fwd_decl")) { + moveClassForwardDeclaration(Result, cast(TemplateFwdDecl)); + } else if (const auto *UsingWithShadow = + Result.Nodes.getNodeAs("using_with_shadow")) { + fixUsingShadowDecl(Result, UsingWithShadow); + } else if (const auto *Specifier = + Result.Nodes.getNodeAs( + "nested_specifier_loc")) { + SourceLocation Start = Specifier->getBeginLoc(); + SourceLocation End = endLocationForType(Specifier->getTypeLoc()); + fixTypeLoc(Result, Start, End, Specifier->getTypeLoc()); + } else if (const auto *BaseInitializer = + Result.Nodes.getNodeAs( + "base_initializer")) { + BaseCtorInitializerTypeLocs.push_back( + BaseInitializer->getTypeSourceInfo()->getTypeLoc()); + } else if (const auto *TLoc = Result.Nodes.getNodeAs("type")) { + // This avoids fixing types with record types as qualifier, which is not + // filtered by matchers in some cases, e.g. the type is templated. We should + // handle the record type qualifier instead. + TypeLoc Loc = *TLoc; + while (Loc.getTypeLocClass() == TypeLoc::Qualified) + Loc = Loc.getNextTypeLoc(); + if (Loc.getTypeLocClass() == TypeLoc::Elaborated) { + NestedNameSpecifierLoc NestedNameSpecifier = + Loc.castAs().getQualifierLoc(); + const Type *SpecifierType = + NestedNameSpecifier.getNestedNameSpecifier()->getAsType(); + if (SpecifierType && SpecifierType->isRecordType()) + return; + } + fixTypeLoc(Result, startLocationForType(Loc), endLocationForType(Loc), Loc); + } else if (const auto *VarRef = + Result.Nodes.getNodeAs("var_ref")) { + const auto *Var = Result.Nodes.getNodeAs("var_decl"); + assert(Var); + if (Var->getCanonicalDecl()->isStaticDataMember()) + return; + const auto *Context = Result.Nodes.getNodeAs("dc"); + assert(Context && "Empty decl context."); + fixDeclRefExpr(Result, Context->getDeclContext(), + llvm::cast(Var), VarRef); + } else if (const auto *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(); + SourceLocation AfterSemi = Lexer::findLocationAfterToken( + End, tok::semi, *Result.SourceManager, 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, "", *Result.SourceManager, + &FileToReplacements); + llvm::StringRef Code = Lexer::getSourceText( + CharSourceRange::getTokenRange( + Result.SourceManager->getSpellingLoc(Start), + Result.SourceManager->getSpellingLoc(End)), + *Result.SourceManager, 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(NsDecl->decls_begin()->getLocStart(), + Code, *Result.SourceManager); + 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()); + // If the symbol is already fully qualified, no change needs to be make. + if (NestedName.startswith("::")) + return; + std::string FromDeclName = FromDecl->getQualifiedNameAsString(); + 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 == FromDecl) { + 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) + return; + // If the reference need to be fully-qualified, add a leading "::" unless + // NewNamespace is the global namespace. + if (ReplaceName == FromDeclName && !NewNamespace.empty()) + 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; + // 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); + format::FormatStyle Style = + format::getStyle("file", FilePath, FallbackStyle); + // 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..5161591db --- /dev/null +++ b/change-namespace/ChangeNamespace.h @@ -0,0 +1,172 @@ +//===-- 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, + 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; +}; + +} // namespace change_namespace +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H diff --git a/change-namespace/tool/CMakeLists.txt b/change-namespace/tool/CMakeLists.txt new file mode 100644 index 000000000..62c412a10 --- /dev/null +++ b/change-namespace/tool/CMakeLists.txt @@ -0,0 +1,23 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-change-namespace + ClangChangeNamespace.cpp + ) +target_link_libraries(clang-change-namespace + clangAST + clangASTMatchers + clangBasic + clangChangeNamespace + clangFormat + clangFrontend + clangRewrite + clangTooling + clangToolingCore + ) + +install(TARGETS clang-change-namespace + RUNTIME DESTINATION bin) diff --git a/change-namespace/tool/ClangChangeNamespace.cpp b/change-namespace/tool/ClangChangeNamespace.cpp new file mode 100644 index 000000000..1cfec1288 --- /dev/null +++ b/change-namespace/tool/ClangChangeNamespace.cpp @@ -0,0 +1,114 @@ +//===-- 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" + +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 Style("style", + cl::desc("The style name used for reformatting."), + cl::init("LLVM"), cl::cat(ChangeNamespaceCategory)); + +} // 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); + change_namespace::ChangeNamespaceTool NamespaceTool( + OldNamespace, NewNamespace, FilePattern, &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(); + + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + + auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + // FIXME: print results in parsable format, e.g. JSON. + outs() << "============== " << File << " ==============\n"; + Rewrite.getEditBuffer(ID).write(llvm::outs()); + outs() << "\n============================================\n"; + } + return 0; +} diff --git a/clang-apply-replacements/CMakeLists.txt b/clang-apply-replacements/CMakeLists.txt new file mode 100644 index 000000000..5366e02d9 --- /dev/null +++ b/clang-apply-replacements/CMakeLists.txt @@ -0,0 +1,19 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangApplyReplacements + lib/Tooling/ApplyReplacements.cpp + + LINK_LIBS + clangAST + clangBasic + clangRewrite + clangToolingCore + ) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + include + ) +add_subdirectory(tool) diff --git a/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h new file mode 100644 index 000000000..5e0ff48a1 --- /dev/null +++ b/clang-apply-replacements/include/clang-apply-replacements/Tooling/ApplyReplacements.h @@ -0,0 +1,155 @@ +//===-- ApplyReplacements.h - Deduplicate and apply replacements -- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the interface for deduplicating, detecting +/// conflicts in, and applying collections of Replacements. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APPLYREPLACEMENTS_H +#define LLVM_CLANG_APPLYREPLACEMENTS_H + +#include "clang/Tooling/Core/Diagnostic.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +namespace clang { + +class DiagnosticsEngine; +class Rewriter; + +namespace format { +struct FormatStyle; +} // end namespace format + +namespace replace { + +/// \brief Collection of source ranges. +typedef std::vector RangeVector; + +/// \brief Collection of TranslationUnitReplacements. +typedef std::vector TUReplacements; + +/// \brief Collection of TranslationUnitReplacement files. +typedef std::vector TUReplacementFiles; + +/// \brief Collection of TranslationUniDiagnostics. +typedef std::vector TUDiagnostics; + +/// \brief Map mapping file name to Replacements targeting that file. +typedef llvm::DenseMap> + FileToReplacementsMap; + +/// \brief Recursively descends through a directory structure rooted at \p +/// Directory and attempts to deserialize *.yaml files as +/// TranslationUnitReplacements. All docs that successfully deserialize are +/// added to \p TUs. +/// +/// Directories starting with '.' are ignored during traversal. +/// +/// \param[in] Directory Directory to begin search for serialized +/// TranslationUnitReplacements. +/// \param[out] TUs Collection of all found and deserialized +/// TranslationUnitReplacements or TranslationUnitDiagnostics. +/// \param[out] TUFiles Collection of all TranslationUnitReplacement files +/// found in \c Directory. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns An error_code indicating success or failure in navigating the +/// directory structure. +std::error_code collectReplacementsFromDirectory( + const llvm::StringRef Directory, TUReplacements &TUs, + TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics); + +std::error_code collectReplacementsFromDirectory( + const llvm::StringRef Directory, TUDiagnostics &TUs, + TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics); + +/// \brief Deduplicate, check for conflicts, and apply all Replacements stored +/// in \c TUs. If conflicts occur, no Replacements are applied. +/// +/// \post For all (key,value) in GroupedReplacements, value[i].getOffset() <= +/// value[i+1].getOffset(). +/// +/// \param[in] TUs Collection of TranslationUnitReplacements or +/// TranslationUnitDiagnostics to merge, +/// deduplicate, and test for conflicts. +/// \param[out] GroupedReplacements Container grouping all Replacements by the +/// file they target. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true If all changes were applied successfully. +/// \li false If there were conflicts. +bool mergeAndDeduplicate(const TUReplacements &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM); + +bool mergeAndDeduplicate(const TUDiagnostics &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM); + +// FIXME: Remove this function after changing clang-apply-replacements to use +// Replacements class. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite); + +/// \brief Apply all replacements in \c GroupedReplacements. +/// +/// \param[in] GroupedReplacements Deduplicated and conflict free Replacements +/// to apply. +/// \param[out] Rewrites The results of applying replacements will be applied +/// to this Rewriter. +/// +/// \returns \parblock +/// \li true If all changes were applied successfully. +/// \li false If a replacement failed to apply. +bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, + clang::Rewriter &Rewrites); + +/// \brief Given a collection of Replacements for a single file, produces a list +/// of source ranges that enclose those Replacements. +/// +/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in] Replacements Replacements from a single file. +/// +/// \returns Collection of source ranges that enclose all given Replacements. +/// One range is created for each replacement. +RangeVector calculateChangedRanges( + const std::vector &Replacements); + +/// \brief Write the contents of \c FileContents to disk. Keys of the map are +/// filenames and values are the new contents for those files. +/// +/// \param[in] Rewrites Rewriter containing written files to write to disk. +bool writeFiles(const clang::Rewriter &Rewrites); + +/// \brief Delete the replacement files. +/// +/// \param[in] Files Replacement files to delete. +/// \param[in] Diagnostics DiagnosticsEngine used for error output. +/// +/// \returns \parblock +/// \li true If all files have been deleted successfully. +/// \li false If at least one or more failures occur when deleting +/// files. +bool deleteReplacementFiles(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics); + +} // end namespace replace +} // end namespace clang + +#endif // LLVM_CLANG_APPLYREPLACEMENTS_H diff --git a/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp new file mode 100644 index 000000000..52234fed8 --- /dev/null +++ b/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp @@ -0,0 +1,402 @@ +//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the implementation for deduplicating, detecting +/// conflicts in, and applying collections of Replacements. +/// +/// FIXME: Use Diagnostics for output instead of llvm::errs(). +/// +//===----------------------------------------------------------------------===// +#include "clang-apply-replacements/Tooling/ApplyReplacements.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Format/Format.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/DiagnosticsYaml.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace clang; + +static void eatDiagnostics(const SMDiagnostic &, void *) {} + +namespace clang { +namespace replace { + +std::error_code collectReplacementsFromDirectory( + const llvm::StringRef Directory, TUReplacements &TUs, + TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) { + using namespace llvm::sys::fs; + using namespace llvm::sys::path; + + std::error_code ErrorCode; + + for (recursive_directory_iterator I(Directory, ErrorCode), E; + I != E && !ErrorCode; I.increment(ErrorCode)) { + if (filename(I->path())[0] == '.') { + // Indicate not to descend into directories beginning with '.' + I.no_push(); + continue; + } + + if (extension(I->path()) != ".yaml") + continue; + + TUFiles.push_back(I->path()); + + ErrorOr> Out = + MemoryBuffer::getFile(I->path()); + if (std::error_code BufferError = Out.getError()) { + errs() << "Error reading " << I->path() << ": " << BufferError.message() + << "\n"; + continue; + } + + yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); + tooling::TranslationUnitReplacements TU; + YIn >> TU; + if (YIn.error()) { + // File doesn't appear to be a header change description. Ignore it. + continue; + } + + // Only keep files that properly parse. + TUs.push_back(TU); + } + + return ErrorCode; +} + +std::error_code +collectReplacementsFromDirectory(const llvm::StringRef Directory, + TUDiagnostics &TUs, TUReplacementFiles &TUFiles, + clang::DiagnosticsEngine &Diagnostics) { + using namespace llvm::sys::fs; + using namespace llvm::sys::path; + + std::error_code ErrorCode; + + for (recursive_directory_iterator I(Directory, ErrorCode), E; + I != E && !ErrorCode; I.increment(ErrorCode)) { + if (filename(I->path())[0] == '.') { + // Indicate not to descend into directories beginning with '.' + I.no_push(); + continue; + } + + if (extension(I->path()) != ".yaml") + continue; + + TUFiles.push_back(I->path()); + + ErrorOr> Out = + MemoryBuffer::getFile(I->path()); + if (std::error_code BufferError = Out.getError()) { + errs() << "Error reading " << I->path() << ": " << BufferError.message() + << "\n"; + continue; + } + + yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); + tooling::TranslationUnitDiagnostics TU; + YIn >> TU; + if (YIn.error()) { + // File doesn't appear to be a header change description. Ignore it. + continue; + } + + // Only keep files that properly parse. + TUs.push_back(TU); + } + + return ErrorCode; +} + +/// \brief Dumps information for a sequence of conflicting Replacements. +/// +/// \param[in] File FileEntry for the file the conflicting Replacements are +/// for. +/// \param[in] ConflictingReplacements List of conflicting Replacements. +/// \param[in] SM SourceManager used for reporting. +static void reportConflict( + const FileEntry *File, + const llvm::ArrayRef ConflictingReplacements, + SourceManager &SM) { + FileID FID = SM.translateFile(File); + if (FID.isInvalid()) + FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User); + + // FIXME: Output something a little more user-friendly (e.g. unified diff?) + errs() << "The following changes conflict:\n"; + for (const tooling::Replacement &R : ConflictingReplacements) { + if (R.getLength() == 0) { + errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":" + << SM.getColumnNumber(FID, R.getOffset()) << " " + << R.getReplacementText() << "\n"; + } else { + if (R.getReplacementText().empty()) + errs() << " Remove "; + else + errs() << " Replace "; + + errs() << SM.getLineNumber(FID, R.getOffset()) << ":" + << SM.getColumnNumber(FID, R.getOffset()) << "-" + << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":" + << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1); + + if (R.getReplacementText().empty()) + errs() << "\n"; + else + errs() << " with \"" << R.getReplacementText() << "\"\n"; + } + } +} + +// FIXME: Remove this function after changing clang-apply-replacements to use +// Replacements class. +bool applyAllReplacements(const std::vector &Replaces, + Rewriter &Rewrite) { + bool Result = true; + for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +// FIXME: moved from libToolingCore. remove this when std::vector +// is replaced with tooling::Replacements class. +static void deduplicate(std::vector &Replaces, + std::vector &Conflicts) { + if (Replaces.empty()) + return; + + auto LessNoPath = [](const tooling::Replacement &LHS, + const tooling::Replacement &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + if (LHS.getLength() != RHS.getLength()) + return LHS.getLength() < RHS.getLength(); + return LHS.getReplacementText() < RHS.getReplacementText(); + }; + + auto EqualNoPath = [](const tooling::Replacement &LHS, + const tooling::Replacement &RHS) { + return LHS.getOffset() == RHS.getOffset() && + LHS.getLength() == RHS.getLength() && + LHS.getReplacementText() == RHS.getReplacementText(); + }; + + // Deduplicate. We don't want to deduplicate based on the path as we assume + // that all replacements refer to the same file (or are symlinks). + std::sort(Replaces.begin(), Replaces.end(), LessNoPath); + Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), + Replaces.end()); + + // Detect conflicts + tooling::Range ConflictRange(Replaces.front().getOffset(), + Replaces.front().getLength()); + unsigned ConflictStart = 0; + unsigned ConflictLength = 1; + for (unsigned i = 1; i < Replaces.size(); ++i) { + tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); + if (ConflictRange.overlapsWith(Current)) { + // Extend conflicted range + ConflictRange = + tooling::Range(ConflictRange.getOffset(), + std::max(ConflictRange.getLength(), + Current.getOffset() + Current.getLength() - + ConflictRange.getOffset())); + ++ConflictLength; + } else { + if (ConflictLength > 1) + Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength)); + ConflictRange = Current; + ConflictStart = i; + ConflictLength = 1; + } + } + + if (ConflictLength > 1) + Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength)); +} + +/// \brief Deduplicates and tests for conflicts among the replacements for each +/// file in \c Replacements. Any conflicts found are reported. +/// +/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in,out] Replacements Container of all replacements grouped by file +/// to be deduplicated and checked for conflicts. +/// \param[in] SM SourceManager required for conflict reporting. +/// +/// \returns \parblock +/// \li true if conflicts were detected +/// \li false if no conflicts were detected +static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, + SourceManager &SM) { + bool conflictsFound = false; + + for (auto &FileAndReplacements : Replacements) { + const FileEntry *Entry = FileAndReplacements.first; + auto &Replacements = FileAndReplacements.second; + assert(Entry != nullptr && "No file entry!"); + + std::vector Conflicts; + deduplicate(FileAndReplacements.second, Conflicts); + + if (Conflicts.empty()) + continue; + + conflictsFound = true; + + errs() << "There are conflicting changes to " << Entry->getName() << ":\n"; + + for (const tooling::Range &Conflict : Conflicts) { + auto ConflictingReplacements = llvm::makeArrayRef( + &Replacements[Conflict.getOffset()], Conflict.getLength()); + reportConflict(Entry, ConflictingReplacements, SM); + } + } + + return conflictsFound; +} + +bool mergeAndDeduplicate(const TUReplacements &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM) { + + // Group all replacements by target file. + std::set Warned; + for (const auto &TU : TUs) { + for (const tooling::Replacement &R : TU.Replacements) { + // Use the file manager to deduplicate paths. FileEntries are + // automatically canonicalized. + const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath()); + if (!Entry && Warned.insert(R.getFilePath()).second) { + errs() << "Described file '" << R.getFilePath() + << "' doesn't exist. Ignoring...\n"; + continue; + } + GroupedReplacements[Entry].push_back(R); + } + } + + // Ask clang to deduplicate and report conflicts. + return !deduplicateAndDetectConflicts(GroupedReplacements, SM); +} + +bool mergeAndDeduplicate(const TUDiagnostics &TUs, + FileToReplacementsMap &GroupedReplacements, + clang::SourceManager &SM) { + + // Group all replacements by target file. + std::set Warned; + for (const auto &TU : TUs) { + for (const auto &D : TU.Diagnostics) { + for (const auto &Fix : D.Fix) { + for (const tooling::Replacement &R : Fix.second) { + // Use the file manager to deduplicate paths. FileEntries are + // automatically canonicalized. + const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath()); + if (!Entry && Warned.insert(R.getFilePath()).second) { + errs() << "Described file '" << R.getFilePath() + << "' doesn't exist. Ignoring...\n"; + continue; + } + GroupedReplacements[Entry].push_back(R); + } + } + } + } + + // Ask clang to deduplicate and report conflicts. + return !deduplicateAndDetectConflicts(GroupedReplacements, SM); +} + +bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, + clang::Rewriter &Rewrites) { + + // Apply all changes + // + // FIXME: No longer certain GroupedReplacements is really the best kind of + // data structure for applying replacements. Rewriter certainly doesn't care. + // However, until we nail down the design of ReplacementGroups, might as well + // leave this as is. + for (const auto &FileAndReplacements : GroupedReplacements) { + if (!applyAllReplacements(FileAndReplacements.second, Rewrites)) + return false; + } + + return true; +} + +RangeVector calculateChangedRanges( + const std::vector &Replaces) { + RangeVector ChangedRanges; + + // Generate the new ranges from the replacements. + int Shift = 0; + for (const tooling::Replacement &R : Replaces) { + unsigned Offset = R.getOffset() + Shift; + unsigned Length = R.getReplacementText().size(); + Shift += Length - R.getLength(); + ChangedRanges.push_back(tooling::Range(Offset, Length)); + } + + return ChangedRanges; +} + +bool writeFiles(const clang::Rewriter &Rewrites) { + + for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end(); + BufferI != BufferE; ++BufferI) { + StringRef FileName = + Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName(); + + std::error_code EC; + llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text); + if (EC) { + errs() << "Warning: Could not write to " << EC.message() << "\n"; + continue; + } + BufferI->second.write(FileStream); + } + + return true; +} + +bool deleteReplacementFiles(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics) { + bool Success = true; + for (const auto &Filename : Files) { + std::error_code Error = llvm::sys::fs::remove(Filename); + if (Error) { + Success = false; + // FIXME: Use Diagnostics for outputting errors. + errs() << "Error deleting file: " << Filename << "\n"; + errs() << Error.message() << "\n"; + errs() << "Please delete the file manually\n"; + } + } + return Success; +} + +} // end namespace replace +} // end namespace clang diff --git a/clang-apply-replacements/tool/CMakeLists.txt b/clang-apply-replacements/tool/CMakeLists.txt new file mode 100644 index 000000000..b5c159da1 --- /dev/null +++ b/clang-apply-replacements/tool/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-apply-replacements + ClangApplyReplacementsMain.cpp + ) +target_link_libraries(clang-apply-replacements + clangApplyReplacements + clangBasic + clangFormat + clangRewrite + clangToolingCore + ) + +install(TARGETS clang-apply-replacements + RUNTIME DESTINATION bin) diff --git a/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp new file mode 100644 index 000000000..0881e677d --- /dev/null +++ b/clang-apply-replacements/tool/ClangApplyReplacementsMain.cpp @@ -0,0 +1,283 @@ +//===-- ClangApplyReplacementsMain.cpp - Main file for the tool -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief This file provides the main function for the +/// clang-apply-replacements tool. +/// +//===----------------------------------------------------------------------===// + +#include "clang-apply-replacements/Tooling/ApplyReplacements.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Version.h" +#include "clang/Format/Format.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/CommandLine.h" + +using namespace llvm; +using namespace clang; +using namespace clang::replace; + +static cl::opt Directory(cl::Positional, cl::Required, + cl::desc("")); + +static cl::OptionCategory ReplacementCategory("Replacement Options"); +static cl::OptionCategory FormattingCategory("Formatting Options"); + +const cl::OptionCategory *VisibleCategories[] = {&ReplacementCategory, + &FormattingCategory}; + +static cl::opt RemoveTUReplacementFiles( + "remove-change-desc-files", + cl::desc("Remove the change description files regardless of successful\n" + "merging/replacing."), + cl::init(false), cl::cat(ReplacementCategory)); + +static cl::opt DoFormat( + "format", + cl::desc("Enable formatting of code changed by applying replacements.\n" + "Use -style to choose formatting style.\n"), + cl::cat(FormattingCategory)); + +// FIXME: Consider making the default behaviour for finding a style +// configuration file to start the search anew for every file being changed to +// handle situations where the style is different for different parts of a +// project. + +static cl::opt FormatStyleConfig( + "style-config", + cl::desc("Path to a directory containing a .clang-format file\n" + "describing a formatting style to use for formatting\n" + "code when -style=file.\n"), + cl::init(""), cl::cat(FormattingCategory)); + +static cl::opt + FormatStyleOpt("style", cl::desc(format::StyleOptionHelpDescription), + cl::init("LLVM"), cl::cat(FormattingCategory)); + +namespace { +// Helper object to remove the TUReplacement and TUDiagnostic (triggered by +// "remove-change-desc-files" command line option) when exiting current scope. +class ScopedFileRemover { +public: + ScopedFileRemover(const TUReplacementFiles &Files, + clang::DiagnosticsEngine &Diagnostics) + : TURFiles(Files), Diag(Diagnostics) {} + + ~ScopedFileRemover() { deleteReplacementFiles(TURFiles, Diag); } + +private: + const TUReplacementFiles &TURFiles; + clang::DiagnosticsEngine &Diag; +}; +} // namespace + +static void printVersion() { + outs() << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n"; +} + +/// \brief Convenience function to get rewritten content for \c Filename from +/// \c Rewrites. +/// +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \post Replacements.empty() -> Result.empty() +/// +/// \param[in] Replacements Replacements to apply +/// \param[in] Rewrites Rewriter to use to apply replacements. +/// \param[out] Result Contents of the file after applying replacements if +/// replacements were provided. +/// +/// \returns \parblock +/// \li true if all replacements were applied successfully. +/// \li false if at least one replacement failed to apply. +static bool +getRewrittenData(const std::vector &Replacements, + Rewriter &Rewrites, std::string &Result) { + if (Replacements.empty()) + return true; + + if (!applyAllReplacements(Replacements, Rewrites)) + return false; + + SourceManager &SM = Rewrites.getSourceMgr(); + FileManager &Files = SM.getFileManager(); + + StringRef FileName = Replacements.begin()->getFilePath(); + const clang::FileEntry *Entry = Files.getFile(FileName); + assert(Entry && "Expected an existing file"); + FileID ID = SM.translateFile(Entry); + assert(ID.isValid() && "Expected a valid FileID"); + const RewriteBuffer *Buffer = Rewrites.getRewriteBufferFor(ID); + Result = std::string(Buffer->begin(), Buffer->end()); + + return true; +} + +/// \brief Apply \c Replacements and return the new file contents. +/// +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \post Replacements.empty() -> Result.empty() +/// +/// \param[in] Replacements Replacements to apply. +/// \param[out] Result Contents of the file after applying replacements if +/// replacements were provided. +/// \param[in] Diagnostics For diagnostic output. +/// +/// \returns \parblock +/// \li true if all replacements applied successfully. +/// \li false if at least one replacement failed to apply. +static bool +applyReplacements(const std::vector &Replacements, + std::string &Result, DiagnosticsEngine &Diagnostics) { + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + Rewriter Rewrites(SM, LangOptions()); + + return getRewrittenData(Replacements, Rewrites, Result); +} + +/// \brief Apply code formatting to all places where replacements were made. +/// +/// \pre !Replacements.empty(). +/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath(). +/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset(). +/// +/// \param[in] Replacements Replacements that were made to the file. Provided +/// to indicate where changes were made. +/// \param[in] FileData The contents of the file \b after \c Replacements have +/// been applied. +/// \param[out] FormattedFileData The contents of the file after reformatting. +/// \param[in] FormatStyle Style to apply. +/// \param[in] Diagnostics For diagnostic output. +/// +/// \returns \parblock +/// \li true if reformatting replacements were all successfully +/// applied. +/// \li false if at least one reformatting replacement failed to apply. +static bool +applyFormatting(const std::vector &Replacements, + const StringRef FileData, std::string &FormattedFileData, + const format::FormatStyle &FormatStyle, + DiagnosticsEngine &Diagnostics) { + assert(!Replacements.empty() && "Need at least one replacement"); + + RangeVector Ranges = calculateChangedRanges(Replacements); + + StringRef FileName = Replacements.begin()->getFilePath(); + tooling::Replacements R = + format::reformat(FormatStyle, FileData, Ranges, FileName); + + // FIXME: Remove this copy when tooling::Replacements is implemented as a + // vector instead of a set. + std::vector FormattingReplacements; + std::copy(R.begin(), R.end(), back_inserter(FormattingReplacements)); + + if (FormattingReplacements.empty()) { + FormattedFileData = FileData; + return true; + } + + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + SM.overrideFileContents(Files.getFile(FileName), + llvm::MemoryBuffer::getMemBufferCopy(FileData)); + Rewriter Rewrites(SM, LangOptions()); + + return getRewrittenData(FormattingReplacements, Rewrites, FormattedFileData); +} + +int main(int argc, char **argv) { + cl::HideUnrelatedOptions(makeArrayRef(VisibleCategories)); + + cl::SetVersionPrinter(&printVersion); + cl::ParseCommandLineOptions(argc, argv); + + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), DiagOpts.get()); + + // Determine a formatting style from options. + format::FormatStyle FormatStyle; + if (DoFormat) + FormatStyle = format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM"); + + TUReplacements TURs; + TUReplacementFiles TUFiles; + + std::error_code ErrorCode = + collectReplacementsFromDirectory(Directory, TURs, TUFiles, Diagnostics); + + TUDiagnostics TUDs; + TUFiles.clear(); + ErrorCode = + collectReplacementsFromDirectory(Directory, TUDs, TUFiles, Diagnostics); + + if (ErrorCode) { + errs() << "Trouble iterating over directory '" << Directory + << "': " << ErrorCode.message() << "\n"; + return 1; + } + + // Remove the TUReplacementFiles (triggered by "remove-change-desc-files" + // command line option) when exiting main(). + std::unique_ptr Remover; + if (RemoveTUReplacementFiles) + Remover.reset(new ScopedFileRemover(TUFiles, Diagnostics)); + + FileManager Files((FileSystemOptions())); + SourceManager SM(Diagnostics, Files); + + FileToReplacementsMap GroupedReplacements; + if (!mergeAndDeduplicate(TURs, GroupedReplacements, SM)) + return 1; + if (!mergeAndDeduplicate(TUDs, GroupedReplacements, SM)) + return 1; + + Rewriter ReplacementsRewriter(SM, LangOptions()); + + for (const auto &FileAndReplacements : GroupedReplacements) { + // This shouldn't happen but if a file somehow has no replacements skip to + // next file. + if (FileAndReplacements.second.empty()) + continue; + + std::string NewFileData; + StringRef FileName = FileAndReplacements.first->getName(); + if (!applyReplacements(FileAndReplacements.second, NewFileData, + Diagnostics)) { + errs() << "Failed to apply replacements to " << FileName << "\n"; + continue; + } + + // Apply formatting if requested. + if (DoFormat && + !applyFormatting(FileAndReplacements.second, NewFileData, NewFileData, + FormatStyle, Diagnostics)) { + errs() << "Failed to apply reformatting replacements for " << FileName + << "\n"; + continue; + } + + // Write new file to disk + std::error_code EC; + llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Could not open " << FileName << " for writing\n"; + continue; + } + + FileStream << NewFileData; + } + + return 0; +} diff --git a/clang-move/CMakeLists.txt b/clang-move/CMakeLists.txt new file mode 100644 index 000000000..84175fd38 --- /dev/null +++ b/clang-move/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_library(clangMove + ClangMove.cpp + HelperDeclRefGraph.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-move/ClangMove.cpp b/clang-move/ClangMove.cpp new file mode 100644 index 000000000..155c46fdf --- /dev/null +++ b/clang-move/ClangMove.cpp @@ -0,0 +1,883 @@ +//===-- 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_P(Decl, hasOutermostEnclosingClass, + ast_matchers::internal::Matcher, InnerMatcher) { + const auto *Context = Node.getDeclContext(); + if (!Context) + return false; + while (const auto *NextContext = Context->getParent()) { + if (isa(NextContext) || + isa(NextContext)) + break; + Context = NextContext; + } + return InnerMatcher.matches(*Decl::castFromDeclContext(Context), Finder, + Builder); +} + +AST_MATCHER_P(CXXMethodDecl, ofOutermostEnclosingClass, + ast_matchers::internal::Matcher, InnerMatcher) { + const CXXRecordDecl *Parent = Node.getParent(); + if (!Parent) + return false; + while (const auto *NextParent = + dyn_cast(Parent->getParent())) { + Parent = NextParent; + } + + return InnerMatcher.matches(*Parent, Finder, Builder); +} + +// Make the Path absolute using the CurrentDir if the Path is not an absolute +// path. An empty Path will result in an empty string. +std::string MakeAbsolutePath(StringRef CurrentDir, StringRef Path) { + if (Path.empty()) + return ""; + llvm::SmallString<128> InitialDirectory(CurrentDir); + llvm::SmallString<128> AbsolutePath(Path); + if (std::error_code EC = + llvm::sys::fs::make_absolute(InitialDirectory, AbsolutePath)) + llvm::errs() << "Warning: could not make absolute file: '" << EC.message() + << '\n'; + llvm::sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true); + llvm::sys::path::native(AbsolutePath); + return AbsolutePath.str(); +} + +// Make the Path absolute using the current working directory of the given +// SourceManager if the Path is not an absolute path. +// +// The Path can be a path relative to the build directory, or retrieved from +// the SourceManager. +std::string MakeAbsolutePath(const SourceManager &SM, StringRef Path) { + llvm::SmallString<128> AbsolutePath(Path); + if (std::error_code EC = + SM.getFileManager().getVirtualFileSystem()->makeAbsolute( + AbsolutePath)) + llvm::errs() << "Warning: could not make absolute file: '" << EC.message() + << '\n'; + // Handle symbolic link path cases. + // We are trying to get the real file path of the symlink. + const DirectoryEntry *Dir = SM.getFileManager().getDirectory( + llvm::sys::path::parent_path(AbsolutePath.str())); + if (Dir) { + StringRef DirName = SM.getFileManager().getCanonicalName(Dir); + SmallVector AbsoluteFilename; + llvm::sys::path::append(AbsoluteFilename, DirName, + llvm::sys::path::filename(AbsolutePath.str())); + return llvm::StringRef(AbsoluteFilename.data(), AbsoluteFilename.size()) + .str(); + } + return AbsolutePath.str(); +} + +// Matches AST nodes that are expanded within the given AbsoluteFilePath. +AST_POLYMORPHIC_MATCHER_P(isExpansionInFile, + AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc), + std::string, AbsoluteFilePath) { + auto &SourceManager = Finder->getASTContext().getSourceManager(); + auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart()); + if (ExpansionLoc.isInvalid()) + return false; + auto FileEntry = + SourceManager.getFileEntryForID(SourceManager.getFileID(ExpansionLoc)); + if (!FileEntry) + return false; + return MakeAbsolutePath(SourceManager, FileEntry->getName()) == + AbsoluteFilePath; +} + +class FindAllIncludes : public clang::PPCallbacks { +public: + explicit FindAllIncludes(SourceManager *SM, ClangMoveTool *const MoveTool) + : SM(*SM), MoveTool(MoveTool) {} + + void InclusionDirective(clang::SourceLocation HashLoc, + const clang::Token & /*IncludeTok*/, + StringRef FileName, bool IsAngled, + clang::CharSourceRange FilenameRange, + const clang::FileEntry * /*File*/, + StringRef SearchPath, StringRef /*RelativePath*/, + const clang::Module * /*Imported*/) override { + if (const auto *FileEntry = SM.getFileEntryForID(SM.getFileID(HashLoc))) + MoveTool->addIncludes(FileName, IsAngled, SearchPath, + FileEntry->getName(), FilenameRange, SM); + } + +private: + const SourceManager &SM; + ClangMoveTool *const MoveTool; +}; + +/// Add a declatration being moved to new.h/cc. Note that the declaration will +/// also be deleted in old.h/cc. +void MoveDeclFromOldFileToNewFile(ClangMoveTool *MoveTool, const NamedDecl *D) { + MoveTool->getMovedDecls().push_back(D); + MoveTool->addRemovedDecl(D); + MoveTool->getUnremovedDeclsInOldHeader().erase(D); +} + +class FunctionDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit FunctionDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *FD = Result.Nodes.getNodeAs("function"); + assert(FD); + const clang::NamedDecl *D = FD; + if (const auto *FTD = FD->getDescribedFunctionTemplate()) + D = FTD; + MoveDeclFromOldFileToNewFile(MoveTool, D); + } + +private: + ClangMoveTool *MoveTool; +}; + +class TypeAliasMatch : public MatchFinder::MatchCallback { +public: + explicit TypeAliasMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + if (const auto *TD = Result.Nodes.getNodeAs("typedef")) + MoveDeclFromOldFileToNewFile(MoveTool, TD); + else if (const auto *TAD = + Result.Nodes.getNodeAs("type_alias")) { + const NamedDecl * D = TAD; + if (const auto * TD = TAD->getDescribedAliasTemplate()) + D = TD; + MoveDeclFromOldFileToNewFile(MoveTool, D); + } + } + +private: + ClangMoveTool *MoveTool; +}; + +class EnumDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit EnumDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + + void run(const MatchFinder::MatchResult &Result) override { + const auto *ED = Result.Nodes.getNodeAs("enum"); + assert(ED); + MoveDeclFromOldFileToNewFile(MoveTool, ED); + } + +private: + ClangMoveTool *MoveTool; +}; + +class ClassDeclarationMatch : public MatchFinder::MatchCallback { +public: + explicit ClassDeclarationMatch(ClangMoveTool *MoveTool) + : MoveTool(MoveTool) {} + void run(const MatchFinder::MatchResult &Result) override { + clang::SourceManager* SM = &Result.Context->getSourceManager(); + if (const auto *CMD = + Result.Nodes.getNodeAs("class_method")) + MatchClassMethod(CMD, SM); + else if (const auto *VD = Result.Nodes.getNodeAs( + "class_static_var_decl")) + MatchClassStaticVariable(VD, SM); + else if (const auto *CD = Result.Nodes.getNodeAs( + "moved_class")) + MatchClassDeclaration(CD, SM); + } + +private: + void MatchClassMethod(const clang::CXXMethodDecl* CMD, + clang::SourceManager* SM) { + // Skip inline class methods. isInline() ast matcher doesn't ignore this + // case. + if (!CMD->isInlined()) { + MoveTool->getMovedDecls().push_back(CMD); + MoveTool->addRemovedDecl(CMD); + // Get template class method from its method declaration as + // UnremovedDecls stores template class method. + if (const auto *FTD = CMD->getDescribedFunctionTemplate()) + MoveTool->getUnremovedDeclsInOldHeader().erase(FTD); + else + MoveTool->getUnremovedDeclsInOldHeader().erase(CMD); + } + } + + void MatchClassStaticVariable(const clang::NamedDecl *VD, + clang::SourceManager* SM) { + MoveDeclFromOldFileToNewFile(MoveTool, VD); + } + + void MatchClassDeclaration(const clang::CXXRecordDecl *CD, + clang::SourceManager* SM) { + // Get class template from its class declaration as UnremovedDecls stores + // class template. + if (const auto *TC = CD->getDescribedClassTemplate()) + MoveTool->getMovedDecls().push_back(TC); + else + MoveTool->getMovedDecls().push_back(CD); + MoveTool->addRemovedDecl(MoveTool->getMovedDecls().back()); + MoveTool->getUnremovedDeclsInOldHeader().erase( + MoveTool->getMovedDecls().back()); + } + + ClangMoveTool *MoveTool; +}; + +// Expand to get the end location of the line where the EndLoc of the given +// Decl. +SourceLocation +getLocForEndOfDecl(const clang::Decl *D, + const LangOptions &LangOpts = clang::LangOptions()) { + const auto &SM = D->getASTContext().getSourceManager(); + auto EndExpansionLoc = SM.getExpansionLoc(D->getLocEnd()); + std::pair LocInfo = SM.getDecomposedLoc(EndExpansionLoc); + // Try to load the file buffer. + bool InvalidTemp = false; + llvm::StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp); + if (InvalidTemp) + return SourceLocation(); + + const char *TokBegin = File.data() + LocInfo.second; + // Lex from the start of the given location. + Lexer Lex(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(), + TokBegin, File.end()); + + llvm::SmallVector Line; + // FIXME: this is a bit hacky to get ReadToEndOfLine work. + Lex.setParsingPreprocessorDirective(true); + Lex.ReadToEndOfLine(&Line); + SourceLocation EndLoc = EndExpansionLoc.getLocWithOffset(Line.size()); + // If we already reach EOF, just return the EOF SourceLocation; + // otherwise, move 1 offset ahead to include the trailing newline character + // '\n'. + return SM.getLocForEndOfFile(LocInfo.first) == EndLoc + ? EndLoc + : EndLoc.getLocWithOffset(1); +} + +// Get full range of a Decl including the comments associated with it. +clang::CharSourceRange +getFullRange(const clang::Decl *D, + const clang::LangOptions &options = clang::LangOptions()) { + const auto &SM = D->getASTContext().getSourceManager(); + clang::SourceRange Full(SM.getExpansionLoc(D->getLocStart()), + getLocForEndOfDecl(D)); + // Expand to comments that are associated with the Decl. + if (const auto *Comment = D->getASTContext().getRawCommentForDeclNoCache(D)) { + if (SM.isBeforeInTranslationUnit(Full.getEnd(), Comment->getLocEnd())) + Full.setEnd(Comment->getLocEnd()); + // FIXME: Don't delete a preceding comment, if there are no other entities + // it could refer to. + if (SM.isBeforeInTranslationUnit(Comment->getLocStart(), Full.getBegin())) + Full.setBegin(Comment->getLocStart()); + } + + return clang::CharSourceRange::getCharRange(Full); +} + +std::string getDeclarationSourceText(const clang::Decl *D) { + const auto &SM = D->getASTContext().getSourceManager(); + llvm::StringRef SourceText = + clang::Lexer::getSourceText(getFullRange(D), SM, clang::LangOptions()); + return SourceText.str(); +} + +bool isInHeaderFile(const clang::Decl *D, + llvm::StringRef OriginalRunningDirectory, + llvm::StringRef OldHeader) { + const auto &SM = D->getASTContext().getSourceManager(); + if (OldHeader.empty()) + return false; + auto ExpansionLoc = SM.getExpansionLoc(D->getLocStart()); + if (ExpansionLoc.isInvalid()) + return false; + + if (const auto *FE = SM.getFileEntryForID(SM.getFileID(ExpansionLoc))) { + return MakeAbsolutePath(SM, FE->getName()) == + MakeAbsolutePath(OriginalRunningDirectory, OldHeader); + } + + return false; +} + +std::vector getNamespaces(const clang::Decl *D) { + std::vector Namespaces; + for (const auto *Context = D->getDeclContext(); Context; + Context = Context->getParent()) { + if (llvm::isa(Context) || + llvm::isa(Context)) + break; + + if (const auto *ND = llvm::dyn_cast(Context)) + Namespaces.push_back(ND->getName().str()); + } + std::reverse(Namespaces.begin(), Namespaces.end()); + return Namespaces; +} + +clang::tooling::Replacements +createInsertedReplacements(const std::vector &Includes, + const std::vector &Decls, + llvm::StringRef FileName, bool IsHeader = false, + StringRef OldHeaderInclude = "") { + std::string NewCode; + std::string GuardName(FileName); + if (IsHeader) { + for (size_t i = 0; i < GuardName.size(); ++i) { + if (!isAlphanumeric(GuardName[i])) + GuardName[i] = '_'; + } + GuardName = StringRef(GuardName).upper(); + NewCode += "#ifndef " + GuardName + "\n"; + NewCode += "#define " + GuardName + "\n\n"; + } + + NewCode += OldHeaderInclude; + // Add #Includes. + for (const auto &Include : Includes) + NewCode += Include; + + if (!Includes.empty()) + NewCode += "\n"; + + // Add moved class definition and its related declarations. All declarations + // in same namespace are grouped together. + // + // Record namespaces where the current position is in. + std::vector CurrentNamespaces; + for (const auto *MovedDecl : Decls) { + // The namespaces of the declaration being moved. + std::vector DeclNamespaces = getNamespaces(MovedDecl); + auto CurrentIt = CurrentNamespaces.begin(); + auto DeclIt = DeclNamespaces.begin(); + // Skip the common prefix. + while (CurrentIt != CurrentNamespaces.end() && + DeclIt != DeclNamespaces.end()) { + if (*CurrentIt != *DeclIt) + break; + ++CurrentIt; + ++DeclIt; + } + // Calculate the new namespaces after adding MovedDecl in CurrentNamespace, + // which is used for next iteration of this loop. + std::vector NextNamespaces(CurrentNamespaces.begin(), + CurrentIt); + NextNamespaces.insert(NextNamespaces.end(), DeclIt, DeclNamespaces.end()); + + + // End with CurrentNamespace. + bool HasEndCurrentNamespace = false; + auto RemainingSize = CurrentNamespaces.end() - CurrentIt; + for (auto It = CurrentNamespaces.rbegin(); RemainingSize > 0; + --RemainingSize, ++It) { + assert(It < CurrentNamespaces.rend()); + NewCode += "} // namespace " + *It + "\n"; + HasEndCurrentNamespace = true; + } + // Add trailing '\n' after the nested namespace definition. + if (HasEndCurrentNamespace) + NewCode += "\n"; + + // If the moved declaration is not in CurrentNamespace, add extra namespace + // definitions. + bool IsInNewNamespace = false; + while (DeclIt != DeclNamespaces.end()) { + NewCode += "namespace " + *DeclIt + " {\n"; + IsInNewNamespace = true; + ++DeclIt; + } + // If the moved declaration is in same namespace CurrentNamespace, add + // a preceeding `\n' before the moved declaration. + // FIXME: Don't add empty lines between using declarations. + if (!IsInNewNamespace) + NewCode += "\n"; + NewCode += getDeclarationSourceText(MovedDecl); + CurrentNamespaces = std::move(NextNamespaces); + } + std::reverse(CurrentNamespaces.begin(), CurrentNamespaces.end()); + for (const auto &NS : CurrentNamespaces) + NewCode += "} // namespace " + NS + "\n"; + + if (IsHeader) + NewCode += "\n#endif // " + GuardName + "\n"; + return clang::tooling::Replacements( + clang::tooling::Replacement(FileName, 0, 0, NewCode)); +} + +// Return a set of all decls which are used/referenced by the given Decls. +// Specically, given a class member declaration, this method will return all +// decls which are used by the whole class. +llvm::DenseSet +getUsedDecls(const HelperDeclRefGraph *RG, + const std::vector &Decls) { + assert(RG); + llvm::DenseSet Nodes; + for (const auto *D : Decls) { + auto Result = RG->getReachableNodes( + HelperDeclRGBuilder::getOutmostClassOrFunDecl(D)); + Nodes.insert(Result.begin(), Result.end()); + } + llvm::DenseSet Results; + for (const auto *Node : Nodes) + Results.insert(Node->getDecl()); + return Results; +} + +} // namespace + +std::unique_ptr +ClangMoveAction::CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef /*InFile*/) { + Compiler.getPreprocessor().addPPCallbacks(llvm::make_unique( + &Compiler.getSourceManager(), &MoveTool)); + return MatchFinder.newASTConsumer(); +} + +ClangMoveTool::ClangMoveTool(ClangMoveContext *const Context, + DeclarationReporter *const Reporter) + : Context(Context), Reporter(Reporter) { + if (!Context->Spec.NewHeader.empty()) + CCIncludes.push_back("#include \"" + Context->Spec.NewHeader + "\"\n"); +} + +void ClangMoveTool::addRemovedDecl(const NamedDecl *Decl) { + const auto &SM = Decl->getASTContext().getSourceManager(); + auto Loc = Decl->getLocation(); + StringRef FilePath = SM.getFilename(Loc); + FilePathToFileID[FilePath] = SM.getFileID(Loc); + RemovedDecls.push_back(Decl); +} + +void ClangMoveTool::registerMatchers(ast_matchers::MatchFinder *Finder) { + auto InOldHeader = + isExpansionInFile(makeAbsolutePath(Context->Spec.OldHeader)); + auto InOldCC = isExpansionInFile(makeAbsolutePath(Context->Spec.OldCC)); + auto InOldFiles = anyOf(InOldHeader, InOldCC); + auto ForwardDecls = + cxxRecordDecl(unless(anyOf(isImplicit(), isDefinition()))); + 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(ForwardDecls), unless(namespaceDecl()), + unless(usingDirectiveDecl()), // using namespace decl. + unless(classTemplateDecl(has(ForwardDecls))), // template forward decl. + InOldHeader, + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), + hasDeclContext(decl(anyOf(namespaceDecl(), translationUnitDecl())))); + Finder->addMatcher(AllDeclsInHeader.bind("decls_in_header"), this); + + // Don't register other matchers when dumping all declarations in header. + if (Context->DumpDeclarations) + return; + + // Match forward declarations in old header. + Finder->addMatcher(namedDecl(ForwardDecls, 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))) + .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 DefinitionInOldCC = allOf(isDefinition(), unless(InMovedClass), InOldCC); + auto IsOldCCHelperDefinition = + allOf(DefinitionInOldCC, anyOf(isStaticStorageClass(), InAnonymousNS)); + // Match helper classes separately with helper functions/variables since we + // want to reuse these matchers in finding helpers usage below. + auto HelperFuncOrVar = namedDecl(anyOf(functionDecl(IsOldCCHelperDefinition), + varDecl(IsOldCCHelperDefinition))); + auto HelperClasses = cxxRecordDecl(DefinitionInOldCC, 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()); + + // 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("static_decls")) { + MovedDecls.push_back(ND); + } else if (const auto *ND = + Result.Nodes.getNodeAs("helper_decls")) { + MovedDecls.push_back(ND); + HelperDeclarations.push_back(ND); + } else if (const auto *UD = + Result.Nodes.getNodeAs("using_decl")) { + MovedDecls.push_back(UD); + } +} + +std::string ClangMoveTool::makeAbsolutePath(StringRef Path) { + return MakeAbsolutePath(Context->OriginalRunningDirectory, Path); +} + +void ClangMoveTool::addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, + llvm::StringRef SearchPath, + llvm::StringRef FileName, + clang::CharSourceRange IncludeFilenameRange, + const SourceManager &SM) { + SmallVector HeaderWithSearchPath; + llvm::sys::path::append(HeaderWithSearchPath, SearchPath, IncludeHeader); + std::string AbsoluteOldHeader = makeAbsolutePath(Context->Spec.OldHeader); + if (AbsoluteOldHeader == + MakeAbsolutePath(SM, llvm::StringRef(HeaderWithSearchPath.data(), + HeaderWithSearchPath.size()))) { + OldHeaderIncludeRange = IncludeFilenameRange; + return; + } + + std::string IncludeLine = + IsAngled ? ("#include <" + IncludeHeader + ">\n").str() + : ("#include \"" + IncludeHeader + "\"\n").str(); + + std::string AbsoluteCurrentFile = MakeAbsolutePath(SM, FileName); + if (AbsoluteOldHeader == AbsoluteCurrentFile) { + HeaderIncludes.push_back(IncludeLine); + } else if (makeAbsolutePath(Context->Spec.OldCC) == AbsoluteCurrentFile) { + CCIncludes.push_back(IncludeLine); + } +} + +void ClangMoveTool::removeDeclsInOldFiles() { + if (RemovedDecls.empty()) return; + + // If old_header is not specified (only move declarations from old.cc), remain + // all the helper function declarations in old.cc as UnremovedDeclsInOldHeader + // is empty in this case, there is no way to verify unused/used helpers. + if (!Context->Spec.OldHeader.empty()) { + std::vector UnremovedDecls; + for (const auto *D : UnremovedDeclsInOldHeader) + UnremovedDecls.push_back(D); + + auto UsedDecls = getUsedDecls(RGBuilder.getGraph(), UnremovedDecls); + + // We remove the helper declarations which are not used in the old.cc after + // moving the given declarations. + for (const auto *D : HelperDeclarations) { + if (!UsedDecls.count(HelperDeclRGBuilder::getOutmostClassOrFunDecl(D))) { + 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); + format::FormatStyle Style = + format::getStyle("file", FilePath, Context->FallbackStyle); + 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))) + continue; + + DEBUG(llvm::dbgs() << "Helper used in new.cc: " << D->getNameAsString() + << " " << D << "\n"); + ActualNewCCDecls.push_back(D); + } + + if (!Context->Spec.NewHeader.empty()) { + std::string OldHeaderInclude = + Context->Spec.NewDependOnOld + ? "#include \"" + Context->Spec.OldHeader + "\"\n" + : ""; + Context->FileToReplacements[Context->Spec.NewHeader] = + createInsertedReplacements(HeaderIncludes, NewHeaderDecls, + Context->Spec.NewHeader, /*IsHeader=*/true, + OldHeaderInclude); + } + if (!Context->Spec.NewCC.empty()) + Context->FileToReplacements[Context->Spec.NewCC] = + createInsertedReplacements(CCIncludes, ActualNewCCDecls, + Context->Spec.NewCC); +} + +// Move all contents from OldFile to NewFile. +void ClangMoveTool::moveAll(SourceManager &SM, StringRef OldFile, + StringRef NewFile) { + const FileEntry *FE = SM.getFileManager().getFile(makeAbsolutePath(OldFile)); + if (!FE) { + llvm::errs() << "Failed to get file: " << OldFile << "\n"; + return; + } + FileID ID = SM.getOrCreateFileID(FE, SrcMgr::C_User); + auto Begin = SM.getLocForStartOfFile(ID); + auto End = SM.getLocForEndOfFile(ID); + clang::tooling::Replacement RemoveAll ( + SM, clang::CharSourceRange::getCharRange(Begin, End), ""); + std::string FilePath = RemoveAll.getFilePath().str(); + Context->FileToReplacements[FilePath] = + clang::tooling::Replacements(RemoveAll); + + StringRef Code = SM.getBufferData(ID); + if (!NewFile.empty()) { + auto AllCode = clang::tooling::Replacements( + clang::tooling::Replacement(NewFile, 0, 0, Code)); + // If we are moving from old.cc, an extra step is required: excluding + // the #include of "old.h", instead, we replace it with #include of "new.h". + if (Context->Spec.NewCC == NewFile && OldHeaderIncludeRange.isValid()) { + AllCode = AllCode.merge( + clang::tooling::Replacements(clang::tooling::Replacement( + SM, OldHeaderIncludeRange, '"' + Context->Spec.NewHeader + '"'))); + } + Context->FileToReplacements[NewFile] = std::move(AllCode); + } +} + +void ClangMoveTool::onEndOfTranslationUnit() { + if (Context->DumpDeclarations) { + assert(Reporter); + for (const auto *Decl : UnremovedDeclsInOldHeader) { + auto Kind = Decl->getKind(); + const std::string QualifiedName = Decl->getQualifiedNameAsString(); + if (Kind == Decl::Kind::Function || Kind == Decl::Kind::FunctionTemplate) + Reporter->reportDeclaration(QualifiedName, "Function"); + else if (Kind == Decl::Kind::ClassTemplate || + Kind == Decl::Kind::CXXRecord) + Reporter->reportDeclaration(QualifiedName, "Class"); + } + return; + } + + if (RemovedDecls.empty()) + return; + // Ignore symbols that are not supported (e.g. typedef and enum) when + // checking if there is unremoved symbol in old header. This makes sure that + // we always move old files to new files when all symbols produced from + // dump_decls are moved. + auto IsSupportedKind = [](const clang::NamedDecl *Decl) { + switch (Decl->getKind()) { + case Decl::Kind::Function: + case Decl::Kind::FunctionTemplate: + case Decl::Kind::ClassTemplate: + case Decl::Kind::CXXRecord: + case Decl::Kind::Enum: + case Decl::Kind::Typedef: + case Decl::Kind::TypeAlias: + case Decl::Kind::TypeAliasTemplate: + return true; + default: + return false; + } + }; + if (std::none_of(UnremovedDeclsInOldHeader.begin(), + UnremovedDeclsInOldHeader.end(), IsSupportedKind) && + !Context->Spec.OldHeader.empty()) { + auto &SM = RemovedDecls[0]->getASTContext().getSourceManager(); + moveAll(SM, Context->Spec.OldHeader, Context->Spec.NewHeader); + moveAll(SM, Context->Spec.OldCC, Context->Spec.NewCC); + return; + } + DEBUG(RGBuilder.getGraph()->dump()); + moveDeclsToNewFiles(); + removeDeclsInOldFiles(); +} + +} // namespace move +} // namespace clang diff --git a/clang-move/ClangMove.h b/clang-move/ClangMove.h new file mode 100644 index 000000000..2522f5390 --- /dev/null +++ b/clang-move/ClangMove.h @@ -0,0 +1,229 @@ +//===-- ClangMove.h - Clang move -----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H + +#include "HelperDeclRefGraph.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringMap.h" +#include +#include +#include +#include + +namespace clang { +namespace move { + +// A reporter which collects and reports declarations in old header. +class DeclarationReporter { +public: + DeclarationReporter() = default; + ~DeclarationReporter() = default; + + void reportDeclaration(llvm::StringRef DeclarationName, + llvm::StringRef Type) { + DeclarationList.emplace_back(DeclarationName, Type); + }; + + // A pair. + // The DeclarationName is a fully qualified name for the declaration, like + // A::B::Foo. The DeclarationKind is a string represents the kind of the + // declaration, currently only "Function" and "Class" are supported. + typedef std::pair DeclarationPair; + + const std::vector getDeclarationList() const { + return DeclarationList; + } + +private: + std::vector DeclarationList; +}; + +// Specify declarations being moved. It contains all information of the moved +// declarations. +struct MoveDefinitionSpec { + // The list of fully qualified names, e.g. Foo, a::Foo, b::Foo. + SmallVector Names; + // The file path of old header, can be relative path and absolute path. + std::string OldHeader; + // The file path of old cc, can be relative path and absolute path. + std::string OldCC; + // The file path of new header, can be relative path and absolute path. + std::string NewHeader; + // The file path of new cc, can be relative path and absolute path. + std::string NewCC; + // Whether old.h depends on new.h. If true, #include "new.h" will be added + // in old.h. + bool OldDependOnNew = false; + // Whether new.h depends on old.h. If true, #include "old.h" will be added + // in new.h. + bool NewDependOnOld = false; +}; + +// A Context which contains extra options which are used in ClangMoveTool. +struct ClangMoveContext { + MoveDefinitionSpec Spec; + // The Key is file path, value is the replacements being applied to the file. + std::map &FileToReplacements; + // The original working directory where the local clang-move binary runs. + // + // clang-move will change its current working directory to the build + // directory when analyzing the source file. We save the original working + // directory in order to get the absolute file path for the fields in Spec. + std::string OriginalRunningDirectory; + // The name of a predefined code style. + std::string FallbackStyle; + // Whether dump all declarations in old header. + bool DumpDeclarations; +}; + +// This tool is used to move class/function definitions from the given source +// files (old.h/cc) to new files (new.h/cc). +// The goal of this tool is to make the new/old files as compilable as possible. +// +// When moving a symbol,all used helper declarations (e.g. static +// functions/variables definitions in global/named namespace, +// functions/variables/classes definitions in anonymous namespace) used by the +// moved symbol in old.cc are moved to the new.cc. In addition, all +// using-declarations in old.cc are also moved to new.cc; forward class +// declarations in old.h are also moved to new.h. +// +// The remaining helper declarations which are unused by non-moved symbols in +// old.cc will be removed. +// +// Note: When all declarations in old header are being moved, all code in +// old.h/cc will be moved, which means old.h/cc are empty. This ignores symbols +// that are not supported (e.g. typedef and enum) so that we always move old +// files to new files when all symbols produced from dump_decls are moved. +class ClangMoveTool : public ast_matchers::MatchFinder::MatchCallback { +public: + ClangMoveTool(ClangMoveContext *const Context, + DeclarationReporter *const Reporter); + + void registerMatchers(ast_matchers::MatchFinder *Finder); + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + + void onEndOfTranslationUnit() override; + + /// Add #includes from old.h/cc files. + /// + /// \param IncludeHeader The name of the file being included, as written in + /// the source code. + /// \param IsAngled Whether the file name was enclosed in angle brackets. + /// \param SearchPath The search path which was used to find the IncludeHeader + /// in the file system. It can be a relative path or an absolute path. + /// \param FileName The name of file where the IncludeHeader comes from. + /// \param IncludeFilenameRange The source range for the written file name in + /// #include (i.e. "old.h" for #include "old.h") in old.cc. + /// \param SM The SourceManager. + void addIncludes(llvm::StringRef IncludeHeader, bool IsAngled, + llvm::StringRef SearchPath, llvm::StringRef FileName, + clang::CharSourceRange IncludeFilenameRange, + const SourceManager &SM); + + std::vector &getMovedDecls() { return MovedDecls; } + + /// Add declarations being removed from old.h/cc. For each declarations, the + /// method also records the mapping relationship between the corresponding + /// FilePath and its FileID. + void addRemovedDecl(const NamedDecl *Decl); + + llvm::SmallPtrSet &getUnremovedDeclsInOldHeader() { + return UnremovedDeclsInOldHeader; + } + +private: + // Make the Path absolute using the OrignalRunningDirectory if the Path is not + // an absolute path. An empty Path will result in an empty string. + std::string makeAbsolutePath(StringRef Path); + + void removeDeclsInOldFiles(); + void moveDeclsToNewFiles(); + void moveAll(SourceManager& SM, StringRef OldFile, StringRef NewFile); + + // Stores all MatchCallbacks created by this tool. + std::vector> + MatchCallbacks; + // Store all potential declarations (decls being moved, forward decls) that + // might need to move to new.h/cc. It includes all helper declarations + // (include unused ones) by default. The unused ones will be filtered out in + // the last stage. Saving in an AST-visited order. + std::vector MovedDecls; + // The declarations that needs to be removed in old.cc/h. + std::vector RemovedDecls; + // The #includes in old_header.h. + std::vector HeaderIncludes; + // The #includes in old_cc.cc. + std::vector CCIncludes; + // Records all helper declarations (function/variable/class definitions in + // anonymous namespaces, static function/variable definitions in global/named + // namespaces) in old.cc. saving in an AST-visited order. + std::vector HelperDeclarations; + // The unmoved named declarations in old header. + llvm::SmallPtrSet UnremovedDeclsInOldHeader; + /// The source range for the written file name in #include (i.e. "old.h" for + /// #include "old.h") in old.cc, including the enclosing quotes or angle + /// brackets. + clang::CharSourceRange OldHeaderIncludeRange; + /// Mapping from FilePath to FileID, which can be used in post processes like + /// cleanup around replacements. + llvm::StringMap FilePathToFileID; + /// A context contains all running options. It is not owned. + ClangMoveContext *const Context; + /// A reporter to report all declarations from old header. It is not owned. + DeclarationReporter *const Reporter; + /// Builder for helper declarations reference graph. + HelperDeclRGBuilder RGBuilder; +}; + +class ClangMoveAction : public clang::ASTFrontendAction { +public: + ClangMoveAction(ClangMoveContext *const Context, + DeclarationReporter *const Reporter) + : MoveTool(Context, Reporter) { + MoveTool.registerMatchers(&MatchFinder); + } + + ~ClangMoveAction() override = default; + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef InFile) override; + +private: + ast_matchers::MatchFinder MatchFinder; + ClangMoveTool MoveTool; +}; + +class ClangMoveActionFactory : public tooling::FrontendActionFactory { +public: + ClangMoveActionFactory(ClangMoveContext *const Context, + DeclarationReporter *const Reporter = nullptr) + : Context(Context), Reporter(Reporter) {} + + clang::FrontendAction *create() override { + return new ClangMoveAction(Context, Reporter); + } + +private: + // Not owned. + ClangMoveContext *const Context; + DeclarationReporter *const Reporter; +}; + +} // namespace move +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H diff --git a/clang-move/HelperDeclRefGraph.cpp b/clang-move/HelperDeclRefGraph.cpp new file mode 100644 index 000000000..830301517 --- /dev/null +++ b/clang-move/HelperDeclRefGraph.cpp @@ -0,0 +1,128 @@ +//===-- 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 + +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); + + RG->addEdge(getOutmostClassOrFunDecl(DC->getCanonicalDecl()), + getOutmostClassOrFunDecl(FuncRef->getDecl())); + } else if (const auto *UsedClass = + Result.Nodes.getNodeAs("used_class")) { + const auto *DC = Result.Nodes.getNodeAs("dc"); + assert(DC); + RG->addEdge(getOutmostClassOrFunDecl(DC->getCanonicalDecl()), UsedClass); + } +} + +} // namespace move +} // namespace clang diff --git a/clang-move/HelperDeclRefGraph.h b/clang-move/HelperDeclRefGraph.h new file mode 100644 index 000000000..11b3c9c19 --- /dev/null +++ b/clang-move/HelperDeclRefGraph.h @@ -0,0 +1,99 @@ +//===-- UsedHelperDeclFinder.h - AST-based call graph for helper decls ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_USED_HELPER_DECL_FINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_USED_HELPER_DECL_FINDER_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CallGraph.h" +#include "llvm/ADT/DenseSet.h" +#include +#include + +namespace clang { +namespace move { + +// A reference graph for finding used/unused helper declarations in a single +// translation unit (e.g. old.cc). We don't reuse CallGraph in clang/Analysis +// because that CallGraph only supports function declarations. +// +// Helper declarations include following types: +// * function/variable/class definitions in an anonymous namespace. +// * static function/variable definitions in a global/named namespace. +// +// The reference graph is a directed graph. Each node in the graph represents a +// helper declaration in old.cc or a non-moved/moved declaration (e.g. class, +// function) in old.h, which means each node is associated with a Decl. +// +// To construct the graph, we use AST matcher to find interesting Decls (usually +// a pair of Caller and Callee), and add an edge from the Caller node to the +// Callee node. +// +// Specially, for a class, it might have multiple declarations such methods +// and member variables. We only use a single node to present this class, and +// this node is associated with the class declaration (CXXRecordDecl). +// +// The graph has 3 types of edges: +// 1. moved_decl => helper_decl +// 2. non_moved_decl => helper_decl +// 3. helper_decl => helper_decl +class HelperDeclRefGraph { +public: + HelperDeclRefGraph() = default; + ~HelperDeclRefGraph() = default; + + // Add a directed edge from the caller node to the callee node. + // A new node will be created if the node for Caller/Callee doesn't exist. + // + // Note that, all class member declarations are represented by a single node + // in the graph. The corresponding Decl of this node is the class declaration. + void addEdge(const Decl *Caller, const Decl *Callee); + CallGraphNode *getNode(const Decl *D) const; + + // Get all reachable nodes in the graph from the given declaration D's node, + // including D. + llvm::DenseSet getReachableNodes(const Decl *D) const; + + // Dump the call graph for debug purpose. + void dump() const; + +private: + void print(raw_ostream &OS) const; + // Lookup a node for the given declaration D. If not found, insert a new + // node into the graph. + CallGraphNode *getOrInsertNode(Decl *D); + + typedef llvm::DenseMap> + DeclMapTy; + + // DeclMap owns all CallGraphNodes. + DeclMapTy DeclMap; +}; + +// A builder helps to construct a call graph of helper declarations. +class HelperDeclRGBuilder : public ast_matchers::MatchFinder::MatchCallback { +public: + HelperDeclRGBuilder() : RG(new HelperDeclRefGraph) {} + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + const HelperDeclRefGraph *getGraph() const { return RG.get(); } + + // Find out the outmost enclosing class/function declaration of a given D. + // For a CXXMethodDecl, get its CXXRecordDecl; For a VarDecl/FunctionDecl, get + // its outmost enclosing FunctionDecl or CXXRecordDecl. + // Return D if not found. + static const Decl *getOutmostClassOrFunDecl(const Decl *D); + +private: + std::unique_ptr RG; +}; + +} // namespace move +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_USED_HELPER_DECL_FINDER_H diff --git a/clang-move/tool/CMakeLists.txt b/clang-move/tool/CMakeLists.txt new file mode 100644 index 000000000..68409159a --- /dev/null +++ b/clang-move/tool/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-move + ClangMoveMain.cpp + ) + +target_link_libraries(clang-move + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangMove + clangRewrite + clangTooling + clangToolingCore + ) diff --git a/clang-move/tool/ClangMoveMain.cpp b/clang-move/tool/ClangMoveMain.cpp new file mode 100644 index 000000000..6a9676ec2 --- /dev/null +++ b/clang-move/tool/ClangMoveMain.cpp @@ -0,0 +1,211 @@ +//===-- ClangMoveMain.cpp - move defintion to new file ----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangMove.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/YAMLTraits.h" +#include +#include + +using namespace clang; +using namespace llvm; + +namespace { + +std::error_code CreateNewFile(const llvm::Twine &path) { + int fd = 0; + if (std::error_code ec = + llvm::sys::fs::openFileForWrite(path, fd, llvm::sys::fs::F_Text)) + return ec; + + return llvm::sys::Process::SafelyCloseFileDescriptor(fd); +} + +cl::OptionCategory ClangMoveCategory("clang-move options"); + +cl::list Names("names", cl::CommaSeparated, + cl::desc("The list of the names of classes being " + "moved, e.g. \"Foo,a::Foo,b::Foo\"."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldHeader("old_header", + cl::desc("The relative/absolute file path of old header."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldCC("old_cc", cl::desc("The relative/absolute file path of old cc."), + cl::cat(ClangMoveCategory)); + +cl::opt + NewHeader("new_header", + cl::desc("The relative/absolute file path of new header."), + cl::cat(ClangMoveCategory)); + +cl::opt + NewCC("new_cc", cl::desc("The relative/absolute file path of new cc."), + cl::cat(ClangMoveCategory)); + +cl::opt + OldDependOnNew("old_depend_on_new", + cl::desc("Whether old header will depend on new header. If " + "true, clang-move will " + "add #include of new header to old header."), + cl::init(false), cl::cat(ClangMoveCategory)); + +cl::opt + NewDependOnOld("new_depend_on_old", + cl::desc("Whether new header will depend on old header. If " + "true, clang-move will " + "add #include of old header to new header."), + cl::init(false), cl::cat(ClangMoveCategory)); + +cl::opt + Style("style", + cl::desc("The style name used for reformatting. Default is \"llvm\""), + cl::init("llvm"), cl::cat(ClangMoveCategory)); + +cl::opt Dump("dump_result", + cl::desc("Dump results in JSON format to stdout."), + cl::cat(ClangMoveCategory)); + +cl::opt DumpDecls( + "dump_decls", + cl::desc("Dump all declarations in old header (JSON format) to stdout. If " + "the option is specified, other command options will be ignored. " + "An empty JSON will be returned if old header isn't specified."), + cl::cat(ClangMoveCategory)); + +} // namespace + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + tooling::CommonOptionsParser OptionsParser(argc, argv, ClangMoveCategory); + + if (OldDependOnNew && NewDependOnOld) { + llvm::errs() << "Provide either --old_depend_on_new or " + "--new_depend_on_old. clang-move doesn't support these two " + "options at same time (It will introduce include cycle).\n"; + return 1; + } + + tooling::RefactoringTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + // Add "-fparse-all-comments" compile option to make clang parse all comments. + Tool.appendArgumentsAdjuster(tooling::getInsertArgumentAdjuster( + "-fparse-all-comments", tooling::ArgumentInsertPosition::BEGIN)); + move::MoveDefinitionSpec Spec; + Spec.Names = {Names.begin(), Names.end()}; + Spec.OldHeader = OldHeader; + Spec.NewHeader = NewHeader; + Spec.OldCC = OldCC; + Spec.NewCC = NewCC; + Spec.OldDependOnNew = OldDependOnNew; + Spec.NewDependOnOld = NewDependOnOld; + + llvm::SmallString<128> InitialDirectory; + if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory)) + llvm::report_fatal_error("Cannot detect current path: " + + Twine(EC.message())); + + move::ClangMoveContext Context{Spec, Tool.getReplacements(), + InitialDirectory.str(), Style, DumpDecls}; + move::DeclarationReporter Reporter; + auto Factory = llvm::make_unique( + &Context, &Reporter); + + int CodeStatus = Tool.run(Factory.get()); + 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..b3344dad3 --- /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(R.getBegin(), DiagnosticsEngine::Note, + "\"" + BI->first + "\" binds here", + CharSourceRange::getTokenRange(R), None, + &AST->getSourceManager()); + } + break; + } + case OK_Print: { + OS << "Binding for \"" << BI->first << "\":\n"; + BI->second.print(OS, AST->getASTContext().getPrintingPolicy()); + OS << "\n"; + break; + } + case OK_Dump: { + OS << "Binding for \"" << BI->first << "\":\n"; + BI->second.dump(OS, AST->getSourceManager()); + OS << "\n"; + break; + } + } + } + + if (MI->getMap().empty()) + OS << "No bindings.\n"; + } + } + + OS << MatchCount << (MatchCount == 1 ? " match.\n" : " matches.\n"); + return true; +} + +bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { + if (Value) { + QS.NamedValues[Name] = Value; + } else { + QS.NamedValues.erase(Name); + } + return true; +} + +#ifndef _MSC_VER +const QueryKind SetQueryKind::value; +const QueryKind SetQueryKind::value; +#endif + +} // namespace query +} // namespace clang diff --git a/clang-query/Query.h b/clang-query/Query.h new file mode 100644 index 000000000..b8c59cb35 --- /dev/null +++ b/clang-query/Query.h @@ -0,0 +1,136 @@ +//===--- Query.h - clang-query ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_H + +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/Optional.h" +#include + +namespace clang { +namespace query { + +enum OutputKind { OK_Diag, OK_Print, OK_Dump }; + +enum QueryKind { + QK_Invalid, + QK_NoOp, + QK_Help, + QK_Let, + QK_Match, + QK_SetBool, + QK_SetOutputKind, + QK_Quit +}; + +class QuerySession; + +struct Query : llvm::RefCountedBase { + Query(QueryKind Kind) : Kind(Kind) {} + virtual ~Query(); + + /// Perform the query on \p QS and print output to \p OS. + /// + /// \return false if an error occurs, otherwise return true. + virtual bool run(llvm::raw_ostream &OS, QuerySession &QS) const = 0; + + const QueryKind Kind; +}; + +typedef llvm::IntrusiveRefCntPtr QueryRef; + +/// Any query which resulted in a parse error. The error message is in ErrStr. +struct InvalidQuery : Query { + InvalidQuery(const Twine &ErrStr) : Query(QK_Invalid), ErrStr(ErrStr.str()) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + std::string ErrStr; + + static bool classof(const Query *Q) { return Q->Kind == QK_Invalid; } +}; + +/// No-op query (i.e. a blank line). +struct NoOpQuery : Query { + NoOpQuery() : Query(QK_NoOp) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_NoOp; } +}; + +/// Query for "help". +struct HelpQuery : Query { + HelpQuery() : Query(QK_Help) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_Help; } +}; + +/// Query for "quit". +struct QuitQuery : Query { + QuitQuery() : Query(QK_Quit) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + static bool classof(const Query *Q) { return Q->Kind == QK_Quit; } +}; + +/// Query for "match MATCHER". +struct MatchQuery : Query { + MatchQuery(const ast_matchers::dynamic::DynTypedMatcher &Matcher) + : Query(QK_Match), Matcher(Matcher) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + ast_matchers::dynamic::DynTypedMatcher Matcher; + + static bool classof(const Query *Q) { return Q->Kind == QK_Match; } +}; + +struct LetQuery : Query { + LetQuery(StringRef Name, const ast_matchers::dynamic::VariantValue &Value) + : Query(QK_Let), Name(Name), Value(Value) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + std::string Name; + ast_matchers::dynamic::VariantValue Value; + + static bool classof(const Query *Q) { return Q->Kind == QK_Let; } +}; + +template struct SetQueryKind {}; + +template <> struct SetQueryKind { + static const QueryKind value = QK_SetBool; +}; + +template <> struct SetQueryKind { + static const QueryKind value = QK_SetOutputKind; +}; + +/// Query for "set VAR VALUE". +template struct SetQuery : Query { + SetQuery(T QuerySession::*Var, T Value) + : Query(SetQueryKind::value), Var(Var), Value(Value) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override { + QS.*Var = Value; + return true; + } + + static bool classof(const Query *Q) { + return Q->Kind == SetQueryKind::value; + } + + T QuerySession::*Var; + T Value; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/QueryParser.cpp b/clang-query/QueryParser.cpp new file mode 100644 index 000000000..c64d810a7 --- /dev/null +++ b/clang-query/QueryParser.cpp @@ -0,0 +1,278 @@ +//===---- QueryParser.cpp - clang-query command parser --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "QueryParser.h" +#include "Query.h" +#include "QuerySession.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include + +using namespace llvm; +using namespace clang::ast_matchers::dynamic; + +namespace clang { +namespace query { + +// Lex any amount of whitespace followed by a "word" (any sequence of +// non-whitespace characters) from the start of region [Begin,End). If no word +// is found before End, return StringRef(). Begin is adjusted to exclude the +// lexed region. +StringRef QueryParser::lexWord() { + while (true) { + if (Begin == End) + return StringRef(Begin, 0); + + if (!isWhitespace(*Begin)) + break; + + ++Begin; + } + + const char *WordBegin = Begin; + + while (true) { + ++Begin; + + if (Begin == End || isWhitespace(*Begin)) + return StringRef(WordBegin, Begin - WordBegin); + } +} + +// This is the StringSwitch-alike used by lexOrCompleteWord below. See that +// function for details. +template struct QueryParser::LexOrCompleteWord { + StringSwitch Switch; + + QueryParser *P; + StringRef Word; + // Set to the completion point offset in Word, or StringRef::npos if + // completion point not in Word. + size_t WordCompletionPos; + + LexOrCompleteWord(QueryParser *P, StringRef Word, size_t WCP) + : Switch(Word), P(P), Word(Word), WordCompletionPos(WCP) {} + + template + LexOrCompleteWord &Case(const char (&S)[N], const T &Value, + bool IsCompletion = true) { + StringRef CaseStr(S, N - 1); + + if (WordCompletionPos == StringRef::npos) + Switch.Case(S, Value); + else if (N != 1 && IsCompletion && WordCompletionPos <= CaseStr.size() && + CaseStr.substr(0, WordCompletionPos) == + Word.substr(0, WordCompletionPos)) + P->Completions.push_back(LineEditor::Completion( + (CaseStr.substr(WordCompletionPos) + " ").str(), CaseStr)); + return *this; + } + + T Default(const T &Value) const { return Switch.Default(Value); } +}; + +// Lexes a word and stores it in Word. Returns a LexOrCompleteWord object +// that can be used like a llvm::StringSwitch, but adds cases as possible +// completions if the lexed word contains the completion point. +template +QueryParser::LexOrCompleteWord +QueryParser::lexOrCompleteWord(StringRef &Word) { + Word = lexWord(); + size_t WordCompletionPos = StringRef::npos; + if (CompletionPos && CompletionPos <= Word.data() + Word.size()) { + if (CompletionPos < Word.data()) + WordCompletionPos = 0; + else + WordCompletionPos = CompletionPos - Word.data(); + } + return LexOrCompleteWord(this, Word, WordCompletionPos); +} + +QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { + StringRef ValStr; + unsigned Value = lexOrCompleteWord(ValStr) + .Case("false", 0) + .Case("true", 1) + .Default(~0u); + if (Value == ~0u) { + return new InvalidQuery("expected 'true' or 'false', got '" + ValStr + "'"); + } + return new SetQuery(Var, Value); +} + +QueryRef QueryParser::parseSetOutputKind() { + StringRef ValStr; + unsigned OutKind = lexOrCompleteWord(ValStr) + .Case("diag", OK_Diag) + .Case("print", OK_Print) + .Case("dump", OK_Dump) + .Default(~0u); + if (OutKind == ~0u) { + return new InvalidQuery("expected 'diag', 'print' or 'dump', got '" + + ValStr + "'"); + } + return new SetQuery(&QuerySession::OutKind, OutputKind(OutKind)); +} + +QueryRef QueryParser::endQuery(QueryRef Q) { + const char *Extra = Begin; + if (!lexWord().empty()) + return new InvalidQuery("unexpected extra input: '" + + StringRef(Extra, End - Extra) + "'"); + return Q; +} + +namespace { + +enum ParsedQueryKind { + PQK_Invalid, + PQK_NoOp, + PQK_Help, + PQK_Let, + PQK_Match, + PQK_Set, + PQK_Unlet, + PQK_Quit +}; + +enum ParsedQueryVariable { PQV_Invalid, PQV_Output, PQV_BindRoot }; + +QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) { + std::string ErrStr; + llvm::raw_string_ostream OS(ErrStr); + Diag.printToStreamFull(OS); + return new InvalidQuery(OS.str()); +} + +} // namespace + +QueryRef QueryParser::completeMatcherExpression() { + std::vector Comps = Parser::completeExpression( + StringRef(Begin, End - Begin), CompletionPos - Begin, nullptr, + &QS.NamedValues); + for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) { + Completions.push_back(LineEditor::Completion(I->TypedText, I->MatcherDecl)); + } + return QueryRef(); +} + +QueryRef QueryParser::doParse() { + StringRef CommandStr; + ParsedQueryKind QKind = lexOrCompleteWord(CommandStr) + .Case("", PQK_NoOp) + .Case("help", PQK_Help) + .Case("m", PQK_Match, /*IsCompletion=*/false) + .Case("let", PQK_Let) + .Case("match", PQK_Match) + .Case("set", PQK_Set) + .Case("unlet", PQK_Unlet) + .Case("quit", PQK_Quit) + .Default(PQK_Invalid); + + switch (QKind) { + case PQK_NoOp: + return new NoOpQuery; + + case PQK_Help: + return endQuery(new HelpQuery); + + case PQK_Quit: + return endQuery(new QuitQuery); + + case PQK_Let: { + StringRef Name = lexWord(); + + if (Name.empty()) + return new InvalidQuery("expected variable name"); + + if (CompletionPos) + return completeMatcherExpression(); + + Diagnostics Diag; + ast_matchers::dynamic::VariantValue Value; + if (!Parser::parseExpression(StringRef(Begin, End - Begin), nullptr, + &QS.NamedValues, &Value, &Diag)) { + return makeInvalidQueryFromDiagnostics(Diag); + } + + return new LetQuery(Name, Value); + } + + case PQK_Match: { + if (CompletionPos) + return completeMatcherExpression(); + + Diagnostics Diag; + Optional Matcher = Parser::parseMatcherExpression( + StringRef(Begin, End - Begin), nullptr, &QS.NamedValues, &Diag); + if (!Matcher) { + return makeInvalidQueryFromDiagnostics(Diag); + } + return new MatchQuery(*Matcher); + } + + case PQK_Set: { + StringRef VarStr; + ParsedQueryVariable Var = lexOrCompleteWord(VarStr) + .Case("output", PQV_Output) + .Case("bind-root", PQV_BindRoot) + .Default(PQV_Invalid); + if (VarStr.empty()) + return new InvalidQuery("expected variable name"); + if (Var == PQV_Invalid) + return new InvalidQuery("unknown variable: '" + VarStr + "'"); + + QueryRef Q; + switch (Var) { + case PQV_Output: + Q = parseSetOutputKind(); + break; + case PQV_BindRoot: + Q = parseSetBool(&QuerySession::BindRoot); + break; + case PQV_Invalid: + llvm_unreachable("Invalid query kind"); + } + + return endQuery(Q); + } + + case PQK_Unlet: { + StringRef Name = lexWord(); + + if (Name.empty()) + return new InvalidQuery("expected variable name"); + + return endQuery(new LetQuery(Name, VariantValue())); + } + + case PQK_Invalid: + return new InvalidQuery("unknown command: " + CommandStr); + } + + llvm_unreachable("Invalid query kind"); +} + +QueryRef QueryParser::parse(StringRef Line, const QuerySession &QS) { + return QueryParser(Line, QS).doParse(); +} + +std::vector +QueryParser::complete(StringRef Line, size_t Pos, const QuerySession &QS) { + QueryParser P(Line, QS); + P.CompletionPos = Line.data() + Pos; + + P.doParse(); + return P.Completions; +} + +} // namespace query +} // namespace clang diff --git a/clang-query/QueryParser.h b/clang-query/QueryParser.h new file mode 100644 index 000000000..20591815b --- /dev/null +++ b/clang-query/QueryParser.h @@ -0,0 +1,71 @@ +//===--- QueryParser.h - clang-query ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H + +#include "Query.h" +#include "QuerySession.h" +#include "llvm/LineEditor/LineEditor.h" +#include + +namespace clang { +namespace query { + +class QuerySession; + +class QueryParser { +public: + /// Parse \a Line as a query. + /// + /// \return A QueryRef representing the query, which may be an InvalidQuery. + static QueryRef parse(StringRef Line, const QuerySession &QS); + + /// Compute a list of completions for \a Line assuming a cursor at + /// \param Pos characters past the start of \a Line, ordered from most + /// likely to least likely. + /// + /// \return A vector of completions for \a Line. + static std::vector + complete(StringRef Line, size_t Pos, const QuerySession &QS); + +private: + QueryParser(StringRef Line, const QuerySession &QS) + : Begin(Line.begin()), End(Line.end()), CompletionPos(nullptr), QS(QS) {} + + StringRef lexWord(); + + template struct LexOrCompleteWord; + template LexOrCompleteWord lexOrCompleteWord(StringRef &Str); + + QueryRef parseSetBool(bool QuerySession::*Var); + QueryRef parseSetOutputKind(); + QueryRef completeMatcherExpression(); + + QueryRef endQuery(QueryRef Q); + + /// \brief Parse [\p Begin,\p End). + /// + /// \return A reference to the parsed query object, which may be an + /// \c InvalidQuery if a parse error occurs. + QueryRef doParse(); + + const char *Begin; + const char *End; + + const char *CompletionPos; + std::vector Completions; + + const QuerySession &QS; +}; + +} // namespace query +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H diff --git a/clang-query/QuerySession.h b/clang-query/QuerySession.h new file mode 100644 index 000000000..162289f8c --- /dev/null +++ b/clang-query/QuerySession.h @@ -0,0 +1,40 @@ +//===--- QuerySession.h - clang-query ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H + +#include "Query.h" +#include "clang/ASTMatchers/Dynamic/VariantValue.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringMap.h" + +namespace clang { + +class ASTUnit; + +namespace query { + +/// Represents the state for a particular clang-query session. +class QuerySession { +public: + QuerySession(llvm::ArrayRef> ASTs) + : ASTs(ASTs), OutKind(OK_Diag), BindRoot(true), Terminate(false) {} + + llvm::ArrayRef> ASTs; + OutputKind OutKind; + bool BindRoot; + bool Terminate; + llvm::StringMap NamedValues; +}; + +} // namespace query +} // namespace clang + +#endif diff --git a/clang-query/tool/CMakeLists.txt b/clang-query/tool/CMakeLists.txt new file mode 100644 index 000000000..52af5c87f --- /dev/null +++ b/clang-query/tool/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-query ClangQuery.cpp) +target_link_libraries(clang-query + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + clangQuery + clangTooling + ) + +install(TARGETS clang-query RUNTIME DESTINATION bin) diff --git a/clang-query/tool/ClangQuery.cpp b/clang-query/tool/ClangQuery.cpp new file mode 100644 index 000000000..669dc40e3 --- /dev/null +++ b/clang-query/tool/ClangQuery.cpp @@ -0,0 +1,116 @@ +//===---- ClangQuery.cpp - clang-query tool -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool is for interactive exploration of the Clang AST using AST matchers. +// It currently allows the user to enter a matcher at an interactive prompt and +// view the resulting bindings as diagnostics, AST pretty prints or AST dumps. +// Example session: +// +// $ cat foo.c +// void foo(void) {} +// $ clang-query foo.c -- +// clang-query> match functionDecl() +// +// Match #1: +// +// foo.c:1:1: note: "root" binds here +// void foo(void) {} +// ^~~~~~~~~~~~~~~~~ +// 1 match. +// +//===----------------------------------------------------------------------===// + +#include "Query.h" +#include "QueryParser.h" +#include "QuerySession.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/LineEditor/LineEditor.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Signals.h" +#include +#include + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::dynamic; +using namespace clang::query; +using namespace clang::tooling; +using namespace llvm; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::OptionCategory ClangQueryCategory("clang-query options"); + +static cl::list Commands("c", cl::desc("Specify command to run"), + cl::value_desc("command"), + cl::cat(ClangQueryCategory)); + +static cl::list CommandFiles("f", + cl::desc("Read commands from file"), + cl::value_desc("file"), + cl::cat(ClangQueryCategory)); + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + CommonOptionsParser OptionsParser(argc, argv, ClangQueryCategory); + + if (!Commands.empty() && !CommandFiles.empty()) { + llvm::errs() << argv[0] << ": cannot specify both -c and -f\n"; + return 1; + } + + ClangTool Tool(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + std::vector> ASTs; + if (Tool.buildASTs(ASTs) != 0) + return 1; + + QuerySession QS(ASTs); + + if (!Commands.empty()) { + for (auto I = Commands.begin(), E = Commands.end(); I != E; ++I) { + QueryRef Q = QueryParser::parse(*I, QS); + if (!Q->run(llvm::outs(), QS)) + return 1; + } + } else if (!CommandFiles.empty()) { + for (auto I = CommandFiles.begin(), E = CommandFiles.end(); I != E; ++I) { + std::ifstream Input(I->c_str()); + if (!Input.is_open()) { + llvm::errs() << argv[0] << ": cannot open " << *I << "\n"; + return 1; + } + while (Input.good()) { + std::string Line; + std::getline(Input, Line); + + QueryRef Q = QueryParser::parse(Line, QS); + if (!Q->run(llvm::outs(), QS)) + return 1; + } + } + } else { + LineEditor LE("clang-query"); + LE.setListCompleter([&QS](StringRef Line, size_t Pos) { + return QueryParser::complete(Line, Pos, QS); + }); + while (llvm::Optional Line = LE.readLine()) { + QueryRef Q = QueryParser::parse(*Line, QS); + Q->run(llvm::outs(), QS); + llvm::outs().flush(); + if (QS.Terminate) + break; + } + } + + return 0; +} diff --git a/clang-rename/CMakeLists.txt b/clang-rename/CMakeLists.txt new file mode 100644 index 000000000..427164308 --- /dev/null +++ b/clang-rename/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangRename + USRFinder.cpp + USRFindingAction.cpp + USRLocFinder.cpp + RenamingAction.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangIndex + clangLex + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-rename/RenamingAction.cpp b/clang-rename/RenamingAction.cpp new file mode 100644 index 000000000..08944552a --- /dev/null +++ b/clang-rename/RenamingAction.cpp @@ -0,0 +1,93 @@ +//===--- tools/extra/clang-rename/RenamingAction.cpp - Clang rename tool --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "RenamingAction.h" +#include "USRLocFinder.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer( + const std::vector &NewNames, + const std::vector &PrevNames, + const std::vector> &USRList, + std::map &FileToReplaces, + bool PrintLocations) + : NewNames(NewNames), PrevNames(PrevNames), USRList(USRList), + FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {} + + void HandleTranslationUnit(ASTContext &Context) override { + for (unsigned I = 0; I < NewNames.size(); ++I) + HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]); + } + + void HandleOneRename(ASTContext &Context, const std::string &NewName, + const std::string &PrevName, + const std::vector &USRs) { + const SourceManager &SourceMgr = Context.getSourceManager(); + std::vector RenamingCandidates; + std::vector NewCandidates; + + NewCandidates = + getLocationsOfUSRs(USRs, PrevName, Context.getTranslationUnitDecl()); + RenamingCandidates.insert(RenamingCandidates.end(), NewCandidates.begin(), + NewCandidates.end()); + + unsigned PrevNameLen = PrevName.length(); + for (const auto &Loc : RenamingCandidates) { + if (PrintLocations) { + FullSourceLoc FullLoc(Loc, SourceMgr); + errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(Loc) + << ":" << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << "\n"; + } + // FIXME: better error handling. + tooling::Replacement Replace(SourceMgr, Loc, PrevNameLen, NewName); + llvm::Error Err = FileToReplaces[Replace.getFilePath()].add(Replace); + if (Err) + llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! " + << llvm::toString(std::move(Err)) << "\n"; + } + } + +private: + const std::vector &NewNames, &PrevNames; + const std::vector> &USRList; + std::map &FileToReplaces; + bool PrintLocations; +}; + +std::unique_ptr RenamingAction::newASTConsumer() { + return llvm::make_unique(NewNames, PrevNames, USRList, + FileToReplaces, PrintLocations); +} + +} // namespace rename +} // namespace clang diff --git a/clang-rename/RenamingAction.h b/clang-rename/RenamingAction.h new file mode 100644 index 000000000..654e13600 --- /dev/null +++ b/clang-rename/RenamingAction.h @@ -0,0 +1,48 @@ +//===--- tools/extra/clang-rename/RenamingAction.h - Clang rename tool ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; +class CompilerInstance; + +namespace rename { + +class RenamingAction { +public: + RenamingAction(const std::vector &NewNames, + const std::vector &PrevNames, + const std::vector> &USRList, + std::map &FileToReplaces, + bool PrintLocations = false) + : NewNames(NewNames), PrevNames(PrevNames), USRList(USRList), + FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {} + + std::unique_ptr newASTConsumer(); + +private: + const std::vector &NewNames, &PrevNames; + const std::vector> &USRList; + std::map &FileToReplaces; + bool PrintLocations; +}; + +} // namespace rename +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_RENAMING_ACTION_H diff --git a/clang-rename/USRFinder.cpp b/clang-rename/USRFinder.cpp new file mode 100644 index 000000000..7733c6f08 --- /dev/null +++ b/clang-rename/USRFinder.cpp @@ -0,0 +1,212 @@ +//===--- tools/extra/clang-rename/USRFinder.cpp - Clang rename tool -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements a recursive AST visitor that finds the USR of a symbol at a +/// point. +/// +//===----------------------------------------------------------------------===// + +#include "USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace rename { + +// NamedDeclFindingASTVisitor recursively visits each AST node to find the +// symbol underneath the cursor. +// FIXME: move to seperate .h/.cc file if this gets too large. +namespace { +class NamedDeclFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + // \brief Finds the NamedDecl at a point in the source. + // \param Point the location in the source to search for the NamedDecl. + explicit NamedDeclFindingASTVisitor(const SourceLocation Point, + const ASTContext &Context) + : Result(nullptr), Point(Point), Context(Context) {} + + // \brief Finds the NamedDecl for a name in the source. + // \param Name the fully qualified name. + explicit NamedDeclFindingASTVisitor(const std::string &Name, + const ASTContext &Context) + : Result(nullptr), Name(Name), Context(Context) {} + + // Declaration visitors: + + // \brief Checks if the point falls within the NameDecl. This covers every + // declaration of a named entity that we may come across. Usually, just + // checking if the point lies within the length of the name of the declaration + // and the start location is sufficient. + bool VisitNamedDecl(const NamedDecl *Decl) { + return dyn_cast(Decl) + ? true + : setResult(Decl, Decl->getLocation(), + Decl->getNameAsString().length()); + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + return setResult(Decl, Expr->getLocation(), + Decl->getNameAsString().length()); + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + return setResult(Decl, Expr->getMemberLoc(), + Decl->getNameAsString().length()); + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + const SourceLocation TypeBeginLoc = Loc.getBeginLoc(); + const SourceLocation TypeEndLoc = Lexer::getLocForEndOfToken( + TypeBeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + if (const auto *TemplateTypeParm = + dyn_cast(Loc.getType())) + return setResult(TemplateTypeParm->getDecl(), TypeBeginLoc, TypeEndLoc); + if (const auto *TemplateSpecType = + dyn_cast(Loc.getType())) { + return setResult(TemplateSpecType->getTemplateName().getAsTemplateDecl(), + TypeBeginLoc, TypeEndLoc); + } + return setResult(Loc.getType()->getAsCXXRecordDecl(), TypeBeginLoc, + TypeEndLoc); + } + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + const SourceLocation InitBeginLoc = Initializer->getSourceLocation(), + InitEndLoc = Lexer::getLocForEndOfToken( + InitBeginLoc, 0, Context.getSourceManager(), + Context.getLangOpts()); + if (!setResult(FieldDecl, InitBeginLoc, InitEndLoc)) + return false; + } + } + return true; + } + + // Other: + + const NamedDecl *getNamedDecl() { return Result; } + + // \brief Determines if a namespace qualifier contains the point. + // \returns false on success and sets Result. + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + setResult(Decl, NameLoc.getLocalBeginLoc(), NameLoc.getLocalEndLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + // \brief Sets Result to Decl if the Point is within Start and End. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Start, + SourceLocation End) { + if (!Decl) + return true; + if (Name.empty()) { + // Offset is used to find the declaration. + if (!Start.isValid() || !Start.isFileID() || !End.isValid() || + !End.isFileID() || !isPointWithin(Start, End)) + return true; + } else { + // Fully qualified name is used to find the declaration. + if (Name != Decl->getQualifiedNameAsString()) + return true; + } + Result = Decl; + return false; + } + + // \brief Sets Result to Decl if Point is within Loc and Loc + Offset. + // \returns false on success. + bool setResult(const NamedDecl *Decl, SourceLocation Loc, unsigned Offset) { + // FIXME: Add test for Offset == 0. Add test for Offset - 1 (vs -2 etc). + return Offset == 0 || + setResult(Decl, Loc, Loc.getLocWithOffset(Offset - 1)); + } + + // \brief Determines if the Point is within Start and End. + bool isPointWithin(const SourceLocation Start, const SourceLocation End) { + // FIXME: Add tests for Point == End. + return Point == Start || Point == End || + (Context.getSourceManager().isBeforeInTranslationUnit(Start, + Point) && + Context.getSourceManager().isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result; + const SourceLocation Point; // The location to find the NamedDecl. + const std::string Name; + const ASTContext &Context; +}; +} // namespace + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point) { + const SourceManager &SM = Context.getSourceManager(); + NamedDeclFindingASTVisitor Visitor(Point, Context); + + // Try to be clever about pruning down the number of top-level declarations we + // see. If both start and end is either before or after the point we're + // looking for the point cannot be inside of this decl. Don't even look at it. + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + SourceLocation StartLoc = CurrDecl->getLocStart(); + SourceLocation EndLoc = CurrDecl->getLocEnd(); + if (StartLoc.isValid() && EndLoc.isValid() && + SM.isBeforeInTranslationUnit(StartLoc, Point) != + SM.isBeforeInTranslationUnit(EndLoc, Point)) + Visitor.TraverseDecl(CurrDecl); + } + + NestedNameSpecifierLocFinder Finder(const_cast(Context)); + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + return Visitor.getNamedDecl(); +} + +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name) { + NamedDeclFindingASTVisitor Visitor(Name, Context); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + + return Visitor.getNamedDecl(); +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // namespace rename +} // namespace clang diff --git a/clang-rename/USRFinder.h b/clang-rename/USRFinder.h new file mode 100644 index 000000000..99ecda0dd --- /dev/null +++ b/clang-rename/USRFinder.h @@ -0,0 +1,84 @@ +//===--- tools/extra/clang-rename/USRFinder.h - Clang rename tool ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Methods for determining the USR of a symbol at a location in source +/// code. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include + +using namespace llvm; +using namespace clang::ast_matchers; + +namespace clang { + +class ASTContext; +class Decl; +class SourceLocation; +class NamedDecl; + +namespace rename { + +// Given an AST context and a point, returns a NamedDecl identifying the symbol +// at the point. Returns null if nothing is found at the point. +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point); + +// Given an AST context and a fully qualified name, returns a NamedDecl +// identifying the symbol with a matching name. Returns null if nothing is +// found for the name. +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name); + +// Converts a Decl into a USR. +std::string getUSRForDecl(const Decl *Decl); + +// FIXME: Implement RecursiveASTVisitor::VisitNestedNameSpecifier instead. +class NestedNameSpecifierLocFinder : public MatchFinder::MatchCallback { +public: + explicit NestedNameSpecifierLocFinder(ASTContext &Context) + : Context(Context) {} + + std::vector getNestedNameSpecifierLocations() { + addMatchers(); + Finder.matchAST(Context); + return Locations; + } + +private: + void addMatchers() { + const auto NestedNameSpecifierLocMatcher = + nestedNameSpecifierLoc().bind("nestedNameSpecifierLoc"); + Finder.addMatcher(NestedNameSpecifierLocMatcher, this); + } + + void run(const MatchFinder::MatchResult &Result) override { + const auto *NNS = Result.Nodes.getNodeAs( + "nestedNameSpecifierLoc"); + Locations.push_back(*NNS); + } + + ASTContext &Context; + std::vector Locations; + MatchFinder Finder; +}; + +} // namespace rename +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDER_H diff --git a/clang-rename/USRFindingAction.cpp b/clang-rename/USRFindingAction.cpp new file mode 100644 index 000000000..8fd196677 --- /dev/null +++ b/clang-rename/USRFindingAction.cpp @@ -0,0 +1,226 @@ +//===--- tools/extra/clang-rename/USRFindingAction.cpp - Clang rename tool ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to find USR for the symbol at , as well as +/// all additional USRs. +/// +//===----------------------------------------------------------------------===// + +#include "USRFindingAction.h" +#include "USRFinder.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/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" + +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +namespace { +// \brief NamedDeclFindingConsumer should delegate finding USRs of given Decl to +// AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given +// Decl refers to class and adds USRs of all overridden methods if Decl refers +// to virtual method. +class AdditionalUSRFinder : public RecursiveASTVisitor { +public: + AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context) + : FoundDecl(FoundDecl), Context(Context) {} + + std::vector Find() { + // Fill OverriddenMethods and PartialSpecs storages. + TraverseDecl(Context.getTranslationUnitDecl()); + if (const auto *MethodDecl = dyn_cast(FoundDecl)) { + addUSRsOfOverridenFunctions(MethodDecl); + for (const auto &OverriddenMethod : OverriddenMethods) { + if (checkIfOverriddenFunctionAscends(OverriddenMethod)) + USRSet.insert(getUSRForDecl(OverriddenMethod)); + } + } else if (const auto *RecordDecl = dyn_cast(FoundDecl)) { + handleCXXRecordDecl(RecordDecl); + } else if (const auto *TemplateDecl = + dyn_cast(FoundDecl)) { + handleClassTemplateDecl(TemplateDecl); + } else { + USRSet.insert(getUSRForDecl(FoundDecl)); + } + return std::vector(USRSet.begin(), USRSet.end()); + } + + bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { + if (MethodDecl->isVirtual()) + OverriddenMethods.push_back(MethodDecl); + return true; + } + + bool VisitClassTemplatePartialSpecializationDecl( + const ClassTemplatePartialSpecializationDecl *PartialSpec) { + PartialSpecs.push_back(PartialSpec); + return true; + } + +private: + void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + if (const auto *ClassTemplateSpecDecl = + dyn_cast(RecordDecl)) + handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate()); + addUSRsOfCtorDtors(RecordDecl); + } + + void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl) { + for (const auto *Specialization : TemplateDecl->specializations()) + addUSRsOfCtorDtors(Specialization); + + for (const auto *PartialSpec : PartialSpecs) { + if (PartialSpec->getSpecializedTemplate() == TemplateDecl) + addUSRsOfCtorDtors(PartialSpec); + } + addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl()); + } + + void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + + for (const auto *CtorDecl : RecordDecl->ctors()) + USRSet.insert(getUSRForDecl(CtorDecl)); + + USRSet.insert(getUSRForDecl(RecordDecl->getDestructor())); + USRSet.insert(getUSRForDecl(RecordDecl)); + } + + void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl) { + USRSet.insert(getUSRForDecl(MethodDecl)); + // Recursively visit each OverridenMethod. + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) + addUSRsOfOverridenFunctions(OverriddenMethod); + } + + bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) { + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { + if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) + return true; + return checkIfOverriddenFunctionAscends(OverriddenMethod); + } + return false; + } + + const Decl *FoundDecl; + ASTContext &Context; + std::set USRSet; + std::vector OverriddenMethods; + std::vector PartialSpecs; +}; +} // namespace + +class NamedDeclFindingConsumer : public ASTConsumer { +public: + NamedDeclFindingConsumer(ArrayRef SymbolOffsets, + ArrayRef QualifiedNames, + std::vector &SpellingNames, + std::vector> &USRList, + bool &ErrorOccurred) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + SpellingNames(SpellingNames), USRList(USRList), + ErrorOccurred(ErrorOccurred) {} + +private: + bool FindSymbol(ASTContext &Context, const SourceManager &SourceMgr, + unsigned SymbolOffset, const std::string &QualifiedName) { + DiagnosticsEngine &Engine = Context.getDiagnostics(); + const FileID MainFileID = SourceMgr.getMainFileID(); + + if (SymbolOffset >= SourceMgr.getFileIDSize(MainFileID)) { + ErrorOccurred = true; + unsigned InvalidOffset = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "SourceLocation in file %0 at offset %1 is invalid"); + Engine.Report(SourceLocation(), InvalidOffset) + << SourceMgr.getFileEntryForID(MainFileID)->getName() << SymbolOffset; + return false; + } + + const SourceLocation Point = SourceMgr.getLocForStartOfFile(MainFileID) + .getLocWithOffset(SymbolOffset); + const NamedDecl *FoundDecl = QualifiedName.empty() + ? getNamedDeclAt(Context, Point) + : getNamedDeclFor(Context, QualifiedName); + + if (FoundDecl == nullptr) { + if (QualifiedName.empty()) { + FullSourceLoc FullLoc(Point, SourceMgr); + unsigned CouldNotFindSymbolAt = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "clang-rename could not find symbol (offset %0)"); + Engine.Report(Point, CouldNotFindSymbolAt) << SymbolOffset; + ErrorOccurred = true; + return false; + } + unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID( + DiagnosticsEngine::Error, "clang-rename could not find symbol %0"); + Engine.Report(CouldNotFindSymbolNamed) << QualifiedName; + ErrorOccurred = true; + return false; + } + + // If FoundDecl is a constructor or destructor, we want to instead take + // the Decl of the corresponding class. + if (const auto *CtorDecl = dyn_cast(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + + SpellingNames.push_back(FoundDecl->getNameAsString()); + AdditionalUSRFinder Finder(FoundDecl, Context); + USRList.push_back(Finder.Find()); + return true; + } + + void HandleTranslationUnit(ASTContext &Context) override { + const SourceManager &SourceMgr = Context.getSourceManager(); + for (unsigned Offset : SymbolOffsets) { + if (!FindSymbol(Context, SourceMgr, Offset, "")) + return; + } + for (const std::string &QualifiedName : QualifiedNames) { + if (!FindSymbol(Context, SourceMgr, 0, QualifiedName)) + return; + } + } + + ArrayRef SymbolOffsets; + ArrayRef QualifiedNames; + std::vector &SpellingNames; + std::vector> &USRList; + bool &ErrorOccurred; +}; + +std::unique_ptr USRFindingAction::newASTConsumer() { + return llvm::make_unique( + SymbolOffsets, QualifiedNames, SpellingNames, USRList, ErrorOccurred); +} + +} // namespace rename +} // namespace clang diff --git a/clang-rename/USRFindingAction.h b/clang-rename/USRFindingAction.h new file mode 100644 index 000000000..42c233d1e --- /dev/null +++ b/clang-rename/USRFindingAction.h @@ -0,0 +1,53 @@ +//===--- tools/extra/clang-rename/USRFindingAction.h - Clang rename tool --===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides an action to find all relevant USRs at a point. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/ArrayRef.h" + +#include +#include + +namespace clang { +class ASTConsumer; +class CompilerInstance; +class NamedDecl; + +namespace rename { + +struct USRFindingAction { + USRFindingAction(ArrayRef SymbolOffsets, + ArrayRef QualifiedNames) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + ErrorOccurred(false) {} + std::unique_ptr newASTConsumer(); + + ArrayRef getUSRSpellings() { return SpellingNames; } + ArrayRef> getUSRList() { return USRList; } + bool errorOccurred() { return ErrorOccurred; } + +private: + std::vector SymbolOffsets; + std::vector QualifiedNames; + std::vector SpellingNames; + std::vector> USRList; + bool ErrorOccurred; +}; + +} // namespace rename +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_FINDING_ACTION_H diff --git a/clang-rename/USRLocFinder.cpp b/clang-rename/USRLocFinder.cpp new file mode 100644 index 000000000..48438b690 --- /dev/null +++ b/clang-rename/USRLocFinder.cpp @@ -0,0 +1,167 @@ +//===--- tools/extra/clang-rename/USRLocFinder.cpp - Clang rename tool ----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Mehtods for finding all instances of a USR. Our strategy is very +/// simple; we just compare the USR at every relevant AST node with the one +/// provided. +/// +//===----------------------------------------------------------------------===// + +#include "USRLocFinder.h" +#include "USRFinder.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include + +using namespace llvm; + +namespace clang { +namespace rename { + +namespace { + +// \brief This visitor recursively searches for all instances of a USR in a +// translation unit and stores them for later usage. +class USRLocFindingASTVisitor + : public clang::RecursiveASTVisitor { +public: + explicit USRLocFindingASTVisitor(const std::vector &USRs, + StringRef PrevName, + const ASTContext &Context) + : USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { + } + + // Declaration visitors: + + bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *ConstructorDecl) { + for (const auto *Initializer : ConstructorDecl->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + if (const clang::FieldDecl *FieldDecl = Initializer->getMember()) { + if (USRSet.find(getUSRForDecl(FieldDecl)) != USRSet.end()) + LocationsFound.push_back(Initializer->getSourceLocation()); + } + } + return true; + } + + bool VisitNamedDecl(const NamedDecl *Decl) { + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) + checkAndAddLocation(Decl->getLocation()); + return true; + } + + // Expression visitors: + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { + const SourceManager &Manager = Decl->getASTContext().getSourceManager(); + SourceLocation Location = Manager.getSpellingLoc(Expr->getLocation()); + checkAndAddLocation(Location); + } + + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl().getDecl(); + if (USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) { + const SourceManager &Manager = Decl->getASTContext().getSourceManager(); + SourceLocation Location = Manager.getSpellingLoc(Expr->getMemberLoc()); + checkAndAddLocation(Location); + } + return true; + } + + // Other visitors: + + bool VisitTypeLoc(const TypeLoc Loc) { + if (USRSet.find(getUSRForDecl(Loc.getType()->getAsCXXRecordDecl())) != + USRSet.end()) + checkAndAddLocation(Loc.getBeginLoc()); + if (const auto *TemplateTypeParm = + dyn_cast(Loc.getType())) { + if (USRSet.find(getUSRForDecl(TemplateTypeParm->getDecl())) != + USRSet.end()) + checkAndAddLocation(Loc.getBeginLoc()); + } + return true; + } + + // Non-visitors: + + // \brief Returns a list of unique locations. Duplicate or overlapping + // locations are erroneous and should be reported! + const std::vector &getLocationsFound() const { + return LocationsFound; + } + + // Namespace traversal: + void handleNestedNameSpecifierLoc(NestedNameSpecifierLoc NameLoc) { + while (NameLoc) { + const NamespaceDecl *Decl = + NameLoc.getNestedNameSpecifier()->getAsNamespace(); + if (Decl && USRSet.find(getUSRForDecl(Decl)) != USRSet.end()) + checkAndAddLocation(NameLoc.getLocalBeginLoc()); + NameLoc = NameLoc.getPrefix(); + } + } + +private: + void checkAndAddLocation(SourceLocation Loc) { + const SourceLocation BeginLoc = Loc; + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + StringRef TokenName = + Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + Context.getSourceManager(), Context.getLangOpts()); + size_t Offset = TokenName.find(PrevName); + + // The token of the source location we find actually has the old + // name. + if (Offset != StringRef::npos) + LocationsFound.push_back(BeginLoc.getLocWithOffset(Offset)); + } + + const std::set USRSet; + const std::string PrevName; + std::vector LocationsFound; + const ASTContext &Context; +}; + +} // namespace + +std::vector +getLocationsOfUSRs(const std::vector &USRs, StringRef PrevName, + Decl *Decl) { + USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); + Visitor.TraverseDecl(Decl); + NestedNameSpecifierLocFinder Finder(Decl->getASTContext()); + + for (const auto &Location : Finder.getNestedNameSpecifierLocations()) + Visitor.handleNestedNameSpecifierLoc(Location); + + return Visitor.getLocationsFound(); +} + +} // namespace rename +} // namespace clang diff --git a/clang-rename/USRLocFinder.h b/clang-rename/USRLocFinder.h new file mode 100644 index 000000000..1b5473bd2 --- /dev/null +++ b/clang-rename/USRLocFinder.h @@ -0,0 +1,35 @@ +//===--- tools/extra/clang-rename/USRLocFinder.h - Clang rename tool ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Provides functionality for finding all instances of a USR in a given +/// AST. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H + +#include "clang/AST/AST.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace clang { +namespace rename { + +// FIXME: make this an AST matcher. Wouldn't that be awesome??? I agree! +std::vector +getLocationsOfUSRs(const std::vector &USRs, + llvm::StringRef PrevName, Decl *Decl); + +} // namespace rename +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_RENAME_USR_LOC_FINDER_H diff --git a/clang-rename/tool/CMakeLists.txt b/clang-rename/tool/CMakeLists.txt new file mode 100644 index 000000000..d2109cdca --- /dev/null +++ b/clang-rename/tool/CMakeLists.txt @@ -0,0 +1,19 @@ +add_clang_executable(clang-rename ClangRename.cpp) + +target_link_libraries(clang-rename + clangBasic + clangFrontend + clangRename + clangRewrite + clangTooling + clangToolingCore + ) + +install(TARGETS clang-rename RUNTIME DESTINATION bin) + +install(PROGRAMS clang-rename.py + DESTINATION share/clang + COMPONENT clang-rename) +install(PROGRAMS clang-rename.el + DESTINATION share/clang + COMPONENT clang-rename) diff --git a/clang-rename/tool/ClangRename.cpp b/clang-rename/tool/ClangRename.cpp new file mode 100644 index 000000000..1f42f7f1d --- /dev/null +++ b/clang-rename/tool/ClangRename.cpp @@ -0,0 +1,231 @@ +//===--- tools/extra/clang-rename/ClangRename.cpp - Clang rename 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 implements a clang-rename tool that automatically finds and +/// renames symbols in C++ code. +/// +//===----------------------------------------------------------------------===// + +#include "../RenamingAction.h" +#include "../USRFindingAction.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.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/ReplacementsYaml.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +using namespace llvm; +using namespace clang; + +/// \brief An oldname -> newname rename. +struct RenameAllInfo { + unsigned Offset = 0; + std::string QualifiedName; + std::string NewName; +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(RenameAllInfo) + +namespace llvm { +namespace yaml { + +/// \brief Specialized MappingTraits to describe how a RenameAllInfo is +/// (de)serialized. +template <> struct MappingTraits { + static void mapping(IO &IO, RenameAllInfo &Info) { + IO.mapOptional("Offset", Info.Offset); + IO.mapOptional("QualifiedName", Info.QualifiedName); + IO.mapRequired("NewName", Info.NewName); + } +}; + +} // end namespace yaml +} // end namespace llvm + +static cl::OptionCategory ClangRenameOptions("clang-rename common options"); + +static cl::list SymbolOffsets( + "offset", + cl::desc("Locates the symbol by offset as opposed to :."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); +static cl::opt Inplace("i", cl::desc("Overwrite edited s."), + cl::cat(ClangRenameOptions)); +static cl::list + QualifiedNames("qualified-name", + cl::desc("The fully qualified name of the symbol."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); + +static cl::list + NewNames("new-name", cl::desc("The new name to change the symbol to."), + cl::ZeroOrMore, cl::cat(ClangRenameOptions)); +static cl::opt PrintName( + "pn", + cl::desc("Print the found symbol's name prior to renaming to stderr."), + cl::cat(ClangRenameOptions)); +static cl::opt PrintLocations( + "pl", cl::desc("Print the locations affected by renaming to stderr."), + cl::cat(ClangRenameOptions)); +static cl::opt + ExportFixes("export-fixes", + cl::desc("YAML file to store suggested fixes in."), + cl::value_desc("filename"), cl::cat(ClangRenameOptions)); +static cl::opt + Input("input", cl::desc("YAML file to load oldname-newname pairs from."), + cl::Optional, cl::cat(ClangRenameOptions)); + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser OP(argc, argv, ClangRenameOptions); + + if (!Input.empty()) { + // Populate QualifiedNames and NewNames from a YAML file. + ErrorOr> Buffer = + llvm::MemoryBuffer::getFile(Input); + if (!Buffer) { + errs() << "clang-rename: failed to read " << Input << ": " + << Buffer.getError().message() << "\n"; + return 1; + } + + std::vector Infos; + llvm::yaml::Input YAML(Buffer.get()->getBuffer()); + YAML >> Infos; + for (const auto &Info : Infos) { + if (!Info.QualifiedName.empty()) + QualifiedNames.push_back(Info.QualifiedName); + else + SymbolOffsets.push_back(Info.Offset); + NewNames.push_back(Info.NewName); + } + } + + // Check the arguments for correctness. + if (NewNames.empty()) { + errs() << "clang-rename: -new-name must be specified.\n\n"; + exit(1); + } + + if (SymbolOffsets.empty() == QualifiedNames.empty()) { + errs() << "clang-rename: -offset and -qualified-name can't be present at " + "the same time.\n"; + exit(1); + } + + // Check if NewNames is a valid identifier in C++17. + LangOptions Options; + Options.CPlusPlus = true; + Options.CPlusPlus1z = true; + IdentifierTable Table(Options); + for (const auto &NewName : NewNames) { + auto NewNameTokKind = Table.get(NewName).getTokenID(); + if (!tok::isAnyIdentifier(NewNameTokKind)) { + errs() << "ERROR: new name is not a valid identifier in C++17.\n\n"; + exit(1); + } + } + + if (SymbolOffsets.size() + QualifiedNames.size() != NewNames.size()) { + errs() << "clang-rename: number of symbol offsets(" << SymbolOffsets.size() + << ") + number of qualified names (" << QualifiedNames.size() + << ") must be equal to number of new names(" << NewNames.size() + << ").\n\n"; + cl::PrintHelpMessage(); + exit(1); + } + + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + rename::USRFindingAction FindingAction(SymbolOffsets, QualifiedNames); + Tool.run(tooling::newFrontendActionFactory(&FindingAction).get()); + const std::vector> &USRList = + FindingAction.getUSRList(); + const std::vector &PrevNames = FindingAction.getUSRSpellings(); + if (PrintName) { + for (const auto &PrevName : PrevNames) { + outs() << "clang-rename found name: " << PrevName << '\n'; + } + } + + if (FindingAction.errorOccurred()) { + // Diagnostics are already issued at this point. + exit(1); + } + + // Perform the renaming. + rename::RenamingAction RenameAction(NewNames, PrevNames, USRList, + Tool.getReplacements(), PrintLocations); + std::unique_ptr Factory = + tooling::newFrontendActionFactory(&RenameAction); + int ExitCode; + + if (Inplace) { + ExitCode = Tool.runAndSave(Factory.get()); + } else { + ExitCode = Tool.run(Factory.get()); + + if (!ExportFixes.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'; + exit(1); + } + + // Export replacements. + tooling::TranslationUnitReplacements TUR; + const auto &FileToReplacements = Tool.getReplacements(); + for (const auto &Entry : FileToReplacements) + TUR.Replacements.insert(TUR.Replacements.end(), Entry.second.begin(), + Entry.second.end()); + + yaml::Output YAML(OS); + YAML << TUR; + OS.close(); + exit(0); + } + + // Write every file to stdout. Right now we just barf the files without any + // indication of which files start where, other than that we print the files + // in the same order we see them. + 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()); + } + } + + exit(ExitCode); +} diff --git a/clang-rename/tool/clang-rename.el b/clang-rename/tool/clang-rename.el new file mode 100644 index 000000000..b6c3ed4c6 --- /dev/null +++ b/clang-rename/tool/clang-rename.el @@ -0,0 +1,79 @@ +;;; clang-rename.el --- Renames every occurrence of a symbol found at . -*- lexical-binding: t; -*- + +;; Keywords: tools, c + +;;; Commentary: + +;; To install clang-rename.el make sure the directory of this file is in your +;; `load-path' and add +;; +;; (require 'clang-rename) +;; +;; to your .emacs configuration. + +;;; Code: + +(defgroup clang-rename nil + "Integration with clang-rename" + :group 'c) + +(defcustom clang-rename-binary "clang-rename" + "Path to clang-rename executable." + :type '(file :must-match t) + :group 'clang-rename) + +;;;###autoload +(defun clang-rename (new-name) + "Rename all instances of the symbol at point to NEW-NAME using clang-rename." + (interactive "sEnter a new name: ") + (save-some-buffers :all) + ;; clang-rename should not be combined with other operations when undoing. + (undo-boundary) + (let ((output-buffer (get-buffer-create "*clang-rename*"))) + (with-current-buffer output-buffer (erase-buffer)) + (let ((exit-code (call-process + clang-rename-binary nil output-buffer nil + (format "-offset=%d" + ;; clang-rename wants file (byte) offsets, not + ;; buffer (character) positions. + (clang-rename--bufferpos-to-filepos + ;; Emacs treats one character after a symbol as + ;; part of the symbol, but clang-rename doesn’t. + ;; Use the beginning of the current symbol, if + ;; available, to resolve the inconsistency. + (or (car (bounds-of-thing-at-point 'symbol)) + (point)) + 'exact)) + (format "-new-name=%s" new-name) + "-i" (buffer-file-name)))) + (if (and (integerp exit-code) (zerop exit-code)) + ;; Success; revert current buffer so it gets the modifications. + (progn + (kill-buffer output-buffer) + (revert-buffer :ignore-auto :noconfirm :preserve-modes)) + ;; Failure; append exit code to output buffer and display it. + (let ((message (clang-rename--format-message + "clang-rename failed with %s %s" + (if (integerp exit-code) "exit status" "signal") + exit-code))) + (with-current-buffer output-buffer + (insert ?\n message ?\n)) + (message "%s" message) + (display-buffer output-buffer)))))) + +(defalias 'clang-rename--bufferpos-to-filepos + (if (fboundp 'bufferpos-to-filepos) + 'bufferpos-to-filepos + ;; Emacs 24 doesn’t have ‘bufferpos-to-filepos’, simulate it using + ;; ‘position-bytes’. + (lambda (position &optional _quality _coding-system) + (1- (position-bytes position))))) + +;; ‘format-message’ is new in Emacs 25.1. Provide a fallback for older +;; versions. +(defalias 'clang-rename--format-message + (if (fboundp 'format-message) 'format-message 'format)) + +(provide 'clang-rename) + +;;; clang-rename.el ends here diff --git a/clang-rename/tool/clang-rename.py b/clang-rename/tool/clang-rename.py new file mode 100644 index 000000000..3cc6644ff --- /dev/null +++ b/clang-rename/tool/clang-rename.py @@ -0,0 +1,61 @@ +''' +Minimal clang-rename integration with Vim. + +Before installing make sure one of the following is satisfied: + +* clang-rename is in your PATH +* `g:clang_rename_path` in ~/.vimrc points to valid clang-rename executable +* `binary` in clang-rename.py points to valid to clang-rename executable + +To install, simply put this into your ~/.vimrc + + noremap cr :pyf /clang-rename.py + +IMPORTANT NOTE: Before running the tool, make sure you saved the file. + +All you have to do now is to place a cursor on a variable/function/class which +you would like to rename and press 'cr'. You will be prompted for a new +name if the cursor points to a valid symbol. +''' + +import vim +import subprocess +import sys + +def main(): + binary = 'clang-rename' + if vim.eval('exists("g:clang_rename_path")') == "1": + binary = vim.eval('g:clang_rename_path') + + # Get arguments for clang-rename binary. + offset = int(vim.eval('line2byte(line("."))+col(".")')) - 2 + if offset < 0: + print >> sys.stderr, '''Couldn\'t determine cursor position. + Is your file empty?''' + return + filename = vim.current.buffer.name + + new_name_request_message = 'type new name:' + new_name = vim.eval("input('{}\n')".format(new_name_request_message)) + + # Call clang-rename. + command = [binary, + filename, + '-i', + '-offset', str(offset), + '-new-name', str(new_name)] + # FIXME: make it possible to run the tool on unsaved file. + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + + if stderr: + print stderr + + # Reload all buffers in Vim. + vim.command("checktime") + + +if __name__ == '__main__': + main() diff --git a/clang-reorder-fields/CMakeLists.txt b/clang-reorder-fields/CMakeLists.txt new file mode 100644 index 000000000..51cd4afee --- /dev/null +++ b/clang-reorder-fields/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangReorderFields + ReorderFieldsAction.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangIndex + clangLex + clangToolingCore + ) + +add_subdirectory(tool) diff --git a/clang-reorder-fields/ReorderFieldsAction.cpp b/clang-reorder-fields/ReorderFieldsAction.cpp new file mode 100644 index 000000000..1996380ae --- /dev/null +++ b/clang-reorder-fields/ReorderFieldsAction.cpp @@ -0,0 +1,264 @@ +//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.cpp -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the +/// ReorderFieldsAction::newASTConsumer method +/// +//===----------------------------------------------------------------------===// + +#include "ReorderFieldsAction.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactoring.h" +#include +#include + +namespace clang { +namespace reorder_fields { +using namespace clang::ast_matchers; + +/// \brief Finds the definition of a record by name. +/// +/// \returns nullptr if the name is ambiguous or not found. +static const CXXRecordDecl *findDefinition(StringRef RecordName, + ASTContext &Context) { + auto Results = match( + recordDecl(hasName(RecordName), isDefinition()).bind("cxxRecordDecl"), + Context); + if (Results.empty()) { + llvm::errs() << "Definition of " << RecordName << " not found\n"; + return nullptr; + } + if (Results.size() > 1) { + llvm::errs() << "The name " << RecordName + << " is ambiguous, several definitions found\n"; + return nullptr; + } + return selectFirst("cxxRecordDecl", Results); +} + +/// \brief Calculates the new order of fields. +/// +/// \returns empty vector if the list of fields doesn't match the definition. +static SmallVector +getNewFieldsOrder(const CXXRecordDecl *Definition, + ArrayRef DesiredFieldsOrder) { + assert(Definition && "Definition is null"); + + llvm::StringMap NameToIndex; + for (const auto *Field : Definition->fields()) + NameToIndex[Field->getName()] = Field->getFieldIndex(); + + if (DesiredFieldsOrder.size() != NameToIndex.size()) { + llvm::errs() << "Number of provided fields doesn't match definition.\n"; + return {}; + } + SmallVector NewFieldsOrder; + for (const auto &Name : DesiredFieldsOrder) { + if (!NameToIndex.count(Name)) { + llvm::errs() << "Field " << Name << " not found in definition.\n"; + return {}; + } + NewFieldsOrder.push_back(NameToIndex[Name]); + } + assert(NewFieldsOrder.size() == NameToIndex.size()); + return NewFieldsOrder; +} + +// FIXME: error-handling +/// \brief Replaces one range of source code by another. +static void +addReplacement(SourceRange Old, SourceRange New, const ASTContext &Context, + std::map &Replacements) { + StringRef NewText = + Lexer::getSourceText(CharSourceRange::getTokenRange(New), + Context.getSourceManager(), Context.getLangOpts()); + tooling::Replacement R(Context.getSourceManager(), + CharSourceRange::getTokenRange(Old), NewText, + Context.getLangOpts()); + consumeError(Replacements[R.getFilePath()].add(R)); +} + +/// \brief Reorders fields in the definition of a struct/class. +/// +/// At the moment reodering of fields with +/// different accesses (public/protected/private) is not supported. +/// \returns true on success. +static bool reorderFieldsInDefinition( + const CXXRecordDecl *Definition, ArrayRef NewFieldsOrder, + const ASTContext &Context, + std::map &Replacements) { + assert(Definition && "Definition is null"); + + SmallVector Fields; + for (const auto *Field : Definition->fields()) + Fields.push_back(Field); + + // Check that the permutation of the fields doesn't change the accesses + for (const auto *Field : Definition->fields()) { + const auto FieldIndex = Field->getFieldIndex(); + if (Field->getAccess() != Fields[NewFieldsOrder[FieldIndex]]->getAccess()) { + llvm::errs() << "Currently reodering of fields with different accesses " + "is not supported\n"; + return false; + } + } + + for (const auto *Field : Definition->fields()) { + const auto FieldIndex = Field->getFieldIndex(); + if (FieldIndex == NewFieldsOrder[FieldIndex]) + continue; + addReplacement(Field->getSourceRange(), + Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(), + Context, Replacements); + } + return true; +} + +/// \brief Reorders initializers in a C++ struct/class constructor. +/// +/// A constructor can have initializers for an arbitrary subset of the class's fields. +/// Thus, we need to ensure that we reorder just the initializers that are present. +static void reorderFieldsInConstructor( + const CXXConstructorDecl *CtorDecl, ArrayRef NewFieldsOrder, + const ASTContext &Context, + std::map &Replacements) { + assert(CtorDecl && "Constructor declaration is null"); + if (CtorDecl->isImplicit() || CtorDecl->getNumCtorInitializers() <= 1) + return; + + // The method FunctionDecl::isThisDeclarationADefinition returns false + // for a defaulted function unless that function has been implicitly defined. + // Thus this assert needs to be after the previous checks. + assert(CtorDecl->isThisDeclarationADefinition() && "Not a definition"); + + SmallVector NewFieldsPositions(NewFieldsOrder.size()); + for (unsigned i = 0, e = NewFieldsOrder.size(); i < e; ++i) + NewFieldsPositions[NewFieldsOrder[i]] = i; + + SmallVector OldWrittenInitializersOrder; + SmallVector NewWrittenInitializersOrder; + for (const auto *Initializer : CtorDecl->inits()) { + if (!Initializer->isWritten()) + continue; + OldWrittenInitializersOrder.push_back(Initializer); + NewWrittenInitializersOrder.push_back(Initializer); + } + auto ByFieldNewPosition = [&](const CXXCtorInitializer *LHS, + const CXXCtorInitializer *RHS) { + assert(LHS && RHS); + return NewFieldsPositions[LHS->getMember()->getFieldIndex()] < + NewFieldsPositions[RHS->getMember()->getFieldIndex()]; + }; + std::sort(std::begin(NewWrittenInitializersOrder), + std::end(NewWrittenInitializersOrder), ByFieldNewPosition); + assert(OldWrittenInitializersOrder.size() == + NewWrittenInitializersOrder.size()); + for (unsigned i = 0, e = NewWrittenInitializersOrder.size(); i < e; ++i) + if (OldWrittenInitializersOrder[i] != NewWrittenInitializersOrder[i]) + addReplacement(OldWrittenInitializersOrder[i]->getSourceRange(), + NewWrittenInitializersOrder[i]->getSourceRange(), Context, + Replacements); +} + +/// \brief Reorders initializers in the brace initialization of an aggregate. +/// +/// At the moment partial initialization is not supported. +/// \returns true on success +static bool reorderFieldsInInitListExpr( + const InitListExpr *InitListEx, ArrayRef NewFieldsOrder, + const ASTContext &Context, + std::map &Replacements) { + assert(InitListEx && "Init list expression is null"); + // We care only about InitListExprs which originate from source code. + // Implicit InitListExprs are created by the semantic analyzer. + if (!InitListEx->isExplicit()) + return true; + // The method InitListExpr::getSyntacticForm may return nullptr indicating that + // the current initializer list also serves as its syntactic form. + if (const auto *SyntacticForm = InitListEx->getSyntacticForm()) + InitListEx = SyntacticForm; + // If there are no initializers we do not need to change anything. + if (!InitListEx->getNumInits()) + return true; + if (InitListEx->getNumInits() != NewFieldsOrder.size()) { + llvm::errs() << "Currently only full initialization is supported\n"; + return false; + } + for (unsigned i = 0, e = InitListEx->getNumInits(); i < e; ++i) + if (i != NewFieldsOrder[i]) + addReplacement( + InitListEx->getInit(i)->getSourceRange(), + InitListEx->getInit(NewFieldsOrder[i])->getSourceRange(), Context, + Replacements); + return true; +} + +namespace { +class ReorderingConsumer : public ASTConsumer { + StringRef RecordName; + ArrayRef DesiredFieldsOrder; + std::map &Replacements; + +public: + ReorderingConsumer(StringRef RecordName, + ArrayRef DesiredFieldsOrder, + std::map &Replacements) + : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), + Replacements(Replacements) {} + + ReorderingConsumer(const ReorderingConsumer &) = delete; + ReorderingConsumer &operator=(const ReorderingConsumer &) = delete; + + void HandleTranslationUnit(ASTContext &Context) override { + const CXXRecordDecl *RD = findDefinition(RecordName, Context); + if (!RD) + return; + SmallVector NewFieldsOrder = + getNewFieldsOrder(RD, DesiredFieldsOrder); + if (NewFieldsOrder.empty()) + return; + if (!reorderFieldsInDefinition(RD, NewFieldsOrder, Context, Replacements)) + return; + for (const auto *C : RD->ctors()) + if (const auto *D = dyn_cast(C->getDefinition())) + reorderFieldsInConstructor(cast(D), + NewFieldsOrder, Context, Replacements); + + // We only need to reorder init list expressions for aggregate types. + // For other types the order of constructor parameters is used, + // which we don't change at the moment. + // Now (v0) partial initialization is not supported. + if (RD->isAggregate()) + for (auto Result : + match(initListExpr(hasType(equalsNode(RD))).bind("initListExpr"), + Context)) + if (!reorderFieldsInInitListExpr( + Result.getNodeAs("initListExpr"), NewFieldsOrder, + Context, Replacements)) { + Replacements.clear(); + return; + } + } +}; +} // end anonymous namespace + +std::unique_ptr ReorderFieldsAction::newASTConsumer() { + return llvm::make_unique(RecordName, DesiredFieldsOrder, + Replacements); +} + +} // namespace reorder_fields +} // namespace clang diff --git a/clang-reorder-fields/ReorderFieldsAction.h b/clang-reorder-fields/ReorderFieldsAction.h new file mode 100644 index 000000000..f08c632a0 --- /dev/null +++ b/clang-reorder-fields/ReorderFieldsAction.h @@ -0,0 +1,47 @@ +//===-- tools/extra/clang-reorder-fields/ReorderFieldsAction.h -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declarations of the ReorderFieldsAction class and +/// the FieldPosition struct. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H + +#include "clang/Tooling/Refactoring.h" + +namespace clang { +class ASTConsumer; + +namespace reorder_fields { + +class ReorderFieldsAction { + llvm::StringRef RecordName; + llvm::ArrayRef DesiredFieldsOrder; + std::map &Replacements; + +public: + ReorderFieldsAction( + llvm::StringRef RecordName, + llvm::ArrayRef DesiredFieldsOrder, + std::map &Replacements) + : RecordName(RecordName), DesiredFieldsOrder(DesiredFieldsOrder), + Replacements(Replacements) {} + + ReorderFieldsAction(const ReorderFieldsAction &) = delete; + ReorderFieldsAction &operator=(const ReorderFieldsAction &) = delete; + + std::unique_ptr newASTConsumer(); +}; +} // namespace reorder_fields +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_REORDER_FIELDS_ACTION_H diff --git a/clang-reorder-fields/tool/CMakeLists.txt b/clang-reorder-fields/tool/CMakeLists.txt new file mode 100644 index 000000000..174e71a11 --- /dev/null +++ b/clang-reorder-fields/tool/CMakeLists.txt @@ -0,0 +1,12 @@ +add_clang_executable(clang-reorder-fields ClangReorderFields.cpp) + +target_link_libraries(clang-reorder-fields + clangBasic + clangFrontend + clangReorderFields + clangRewrite + clangTooling + clangToolingCore + ) + +install(TARGETS clang-reorder-fields RUNTIME DESTINATION bin) diff --git a/clang-reorder-fields/tool/ClangReorderFields.cpp b/clang-reorder-fields/tool/ClangReorderFields.cpp new file mode 100644 index 000000000..077e55e83 --- /dev/null +++ b/clang-reorder-fields/tool/ClangReorderFields.cpp @@ -0,0 +1,88 @@ +//===-- tools/extra/clang-reorder-fields/tool/ClangReorderFields.cpp -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the implementation of clang-reorder-fields tool +/// +//===----------------------------------------------------------------------===// + +#include "../ReorderFieldsAction.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include +#include +#include + +using namespace llvm; +using namespace clang; + +cl::OptionCategory ClangReorderFieldsCategory("clang-reorder-fields options"); + +static cl::opt + RecordName("record-name", cl::Required, + cl::desc("The name of the struct/class."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::list FieldsOrder("fields-order", cl::CommaSeparated, + cl::OneOrMore, + cl::desc("The desired fields order."), + cl::cat(ClangReorderFieldsCategory)); + +static cl::opt Inplace("i", cl::desc("Overwrite edited files."), + cl::cat(ClangReorderFieldsCategory)); + +const char Usage[] = "A tool to reorder fields in C/C++ structs/classes.\n"; + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser OP(argc, argv, ClangReorderFieldsCategory, + Usage); + + auto Files = OP.getSourcePathList(); + tooling::RefactoringTool Tool(OP.getCompilations(), Files); + + reorder_fields::ReorderFieldsAction Action(RecordName, FieldsOrder, + Tool.getReplacements()); + + auto Factory = tooling::newFrontendActionFactory(&Action); + + if (Inplace) + return Tool.runAndSave(Factory.get()); + + int ExitCode = Tool.run(Factory.get()); + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + + auto &FileMgr = Tool.getFiles(); + SourceManager Sources(Diagnostics, FileMgr); + Rewriter Rewrite(Sources, DefaultLangOptions); + Tool.applyAllReplacements(Rewrite); + + for (const auto &File : Files) { + const auto *Entry = FileMgr.getFile(File); + const auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User); + Rewrite.getEditBuffer(ID).write(outs()); + } + + return ExitCode; +} diff --git a/clang-tidy-vs/.gitignore b/clang-tidy-vs/.gitignore new file mode 100644 index 000000000..2b0f3e77a --- /dev/null +++ b/clang-tidy-vs/.gitignore @@ -0,0 +1,7 @@ +obj/ +bin/ +.vs/ +Key.snk +clang-tidy.exe +packages/ +*.csproj.user diff --git a/clang-tidy-vs/CMakeLists.txt b/clang-tidy-vs/CMakeLists.txt new file mode 100644 index 000000000..96381bd6b --- /dev/null +++ b/clang-tidy-vs/CMakeLists.txt @@ -0,0 +1,28 @@ +option(BUILD_CLANG_TIDY_VS_PLUGIN "Build clang-tidy VS plugin" OFF) +if (BUILD_CLANG_TIDY_VS_PLUGIN) + add_custom_target(clang_tidy_exe_for_vsix + ${CMAKE_COMMAND} -E copy_if_different + "${LLVM_TOOLS_BINARY_DIR}/clang-tidy.exe" + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/clang-tidy.exe" + DEPENDS clang-tidy) + + add_custom_target(clang_tidy_license + ${CMAKE_COMMAND} -E copy_if_different + "${CLANG_SOURCE_DIR}/LICENSE.TXT" + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/license.txt") + + if (NOT CLANG_TIDY_VS_VERSION) + set(CLANG_TIDY_VS_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}") + endif() + + configure_file("source.extension.vsixmanifest.in" + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/source.extension.vsixmanifest") + + add_custom_target(clang_tidy_vsix ALL + devenv "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy.sln" /Build Release + DEPENDS clang_tidy_exe_for_vsix "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/source.extension.vsixmanifest" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_SOURCE_DIR}/ClangTidy/bin/Release/ClangTidy.vsix" + "${LLVM_TOOLS_BINARY_DIR}/ClangTidy.vsix" + DEPENDS clang_tidy_exe_for_vsix clang_tidy_license) +endif() diff --git a/clang-tidy-vs/ClangTidy.sln b/clang-tidy-vs/ClangTidy.sln new file mode 100644 index 000000000..345eb8303 --- /dev/null +++ b/clang-tidy-vs/ClangTidy.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClangTidy", "ClangTidy\ClangTidy.csproj", "{BE261DA1-36C6-449A-95C5-4653A549170A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE261DA1-36C6-449A-95C5-4653A549170A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE261DA1-36C6-449A-95C5-4653A549170A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE261DA1-36C6-449A-95C5-4653A549170A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE261DA1-36C6-449A-95C5-4653A549170A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/clang-tidy-vs/ClangTidy/CategoryVerb.cs b/clang-tidy-vs/ClangTidy/CategoryVerb.cs new file mode 100644 index 000000000..ef07a8966 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/CategoryVerb.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + /// + /// Allows entire categories of properties to be enabled, disabled, or inherited + /// in one fell swoop. We add properties to each category with the value being + /// this enum, and when the value is selected, we use reflection to find all other + /// properties in the same category and perform the corresponding action. + /// + public enum CategoryVerb + { + None, + Disable, + Enable, + Inherit + } + + public class CategoryVerbConverter : EnumConverter + { + public CategoryVerbConverter() : base(typeof(CategoryVerb)) + { + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string) + { + switch ((string)value) + { + case "Disable Category": + return CategoryVerb.Disable; + case "Enable Category": + return CategoryVerb.Enable; + case "Inherit Category": + return CategoryVerb.Inherit; + case "": + return CategoryVerb.None; + } + } + return base.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value is CategoryVerb && destinationType == typeof(string)) + { + switch ((CategoryVerb)value) + { + case CategoryVerb.Disable: + return "Disable Category"; + case CategoryVerb.Enable: + return "Enable Category"; + case CategoryVerb.Inherit: + return "Inherit Category"; + case CategoryVerb.None: + return String.Empty; + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/CheckDatabase.cs b/clang-tidy-vs/ClangTidy/CheckDatabase.cs new file mode 100644 index 000000000..6b7668887 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/CheckDatabase.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace LLVM.ClangTidy +{ + public class CheckInfo + { + [YamlAlias("Name")] + public string Name { get; set; } + + [YamlAlias("Label")] + public string Label { get; set; } + + [YamlAlias("Description")] + public string Desc { get; set; } + + [YamlAlias("Category")] + public string Category { get; set; } + } + + /// + /// Reads the list of checks from Yaml and builds a description of each one. + /// This list of checks is then used by the PropertyGrid to determine what + /// items to display. + /// + public static class CheckDatabase + { + static CheckInfo[] Checks_ = null; + + class CheckRoot + { + [YamlAlias("Checks")] + public CheckInfo[] Checks { get; set; } + } + + static CheckDatabase() + { + using (StringReader Reader = new StringReader(Resources.ClangTidyChecks)) + { + Deserializer D = new Deserializer(namingConvention: new PascalCaseNamingConvention()); + var Root = D.Deserialize(Reader); + Checks_ = Root.Checks; + + HashSet Names = new HashSet(); + foreach (var Check in Checks_) + { + if (Names.Contains(Check.Name)) + continue; + Names.Add(Check.Name); + } + } + } + + public static IEnumerable Checks + { + get + { + return Checks_; + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/CheckTree.cs b/clang-tidy-vs/ClangTidy/CheckTree.cs new file mode 100644 index 000000000..f3e25830b --- /dev/null +++ b/clang-tidy-vs/ClangTidy/CheckTree.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace LLVM.ClangTidy +{ + /// + /// CheckTree is used to group checks into categories and subcategories. For + /// example, given the following list of checks: + /// + /// llvm-include-order + /// llvm-namespace-comment + /// llvm-twine-local + /// llvm-header-guard + /// google-runtime-member-string-references + /// google-runtime-int + /// google-readability-namespace-comments + /// + /// the corresponding CheckTree would look like this: + /// + /// llvm + /// include-order + /// namespace-comment + /// twine-local + /// header-guard + /// google + /// runtime + /// member-string-references + /// int + /// readability + /// namespace-comments + /// redundant-smartptr-get + /// + /// This is useful when serializing a set of options out to a .clang-tidy file, + /// because we need to decide the most efficient way to serialize the sequence + /// of check commands, when to use wildcards, etc. For example, if everything + /// under google is inherited, we can simply leave that entry out entirely from + /// the .clang-tidy file. On the other hand, if anything is inherited, we *must + /// not* add or remove google-* by wildcard because that, by definition, means + /// the property is no longer inherited. When we can categorize the checks into + /// groups and subgroups like this, it is possible to efficiently serialize to + /// a minimal representative .clang-tidy file. + /// + + public abstract class CheckTreeNode + { + private string Name_; + private CheckTreeNode Parent_; + + protected CheckTreeNode(string Name, CheckTreeNode Parent) + { + Name_ = Name; + Parent_ = Parent; + } + + public string Path + { + get + { + if (Parent_ == null) + return null; + string ParentPath = Parent_.Path; + if (ParentPath == null) + return Name_; + return ParentPath + "-" + Name_; + } + } + + public string Name + { + get + { + return Name_; + } + } + + + public abstract int CountChecks { get; } + public abstract int CountExplicitlyDisabledChecks { get; } + public abstract int CountExplicitlyEnabledChecks { get; } + public abstract int CountInheritedChecks { get; } + } + + public class CheckTree : CheckTreeNode + { + private Dictionary Children_ = new Dictionary(); + public CheckTree() + : base(null, null) + { + + } + + private CheckTree(string Name, CheckTree Parent) + : base(Name, Parent) + { + } + + private void AddLeaf(string Name, DynamicPropertyDescriptor Property) + { + Children_[Name] = new CheckLeaf(Name, this, Property); + } + + private CheckTree AddOrCreateSubgroup(string Name) + { + CheckTreeNode Subgroup = null; + if (Children_.TryGetValue(Name, out Subgroup)) + { + System.Diagnostics.Debug.Assert(Subgroup is CheckTree); + return (CheckTree)Subgroup; + } + + CheckTree SG = new CheckTree(Name, this); + Children_[Name] = SG; + return SG; + } + + public static CheckTree Build(ClangTidyProperties Config) + { + // Since some check names contain dashes in them, it doesn't make sense to + // simply split all check names by dash and construct a huge tree. For + // example, in the check called google-runtime-member-string-references, + // we don't need each of those to be a different subgroup. So instead we + // explicitly specify the common breaking points at which a user might want + // to use a -* and everything else falls as a leaf under one of these + // categories. + // FIXME: This should be configurable without recompilation + CheckTree Root = new CheckTree(); + string[][] Groups = new string[][] { + new string[] {"boost"}, + new string[] {"cert"}, + new string[] {"clang", "diagnostic"}, + new string[] {"cppcoreguidelines", "interfaces"}, + new string[] {"cppcoreguidelines", "pro", "bounds"}, + new string[] {"cppcoreguidelines", "pro", "type"}, + new string[] {"google", "build"}, + new string[] {"google", "readability"}, + new string[] {"google", "runtime"}, + new string[] {"llvm"}, + new string[] {"misc"}, + }; + + foreach (string[] Group in Groups) + { + CheckTree Subgroup = Root; + foreach (string Component in Group) + Subgroup = Subgroup.AddOrCreateSubgroup(Component); + } + + var Props = Config.GetProperties() + .Cast() + .OfType>() + .Where(x => x.Attributes.OfType().Count() > 0) + .Select(x => new KeyValuePair, string>( + x, x.Attributes.OfType().First().CheckName)); + var PropArray = Props.ToArray(); + foreach (var CheckInfo in PropArray) + { + string LeafName = null; + CheckTree Tree = Root.LocateCheckLeafGroup(CheckInfo.Value, out LeafName); + Tree.AddLeaf(LeafName, CheckInfo.Key); + } + return Root; + } + + private CheckTree LocateCheckLeafGroup(string Check, out string LeafName) + { + string[] Components = Check.Split('-'); + string FirstComponent = Components.FirstOrDefault(); + if (FirstComponent == null) + { + LeafName = Check; + return this; + } + + CheckTreeNode Subgroup = null; + if (!Children_.TryGetValue(FirstComponent, out Subgroup)) + { + LeafName = Check; + return this; + } + System.Diagnostics.Debug.Assert(Subgroup is CheckTree); + CheckTree Child = (CheckTree)Subgroup; + string ChildName = Check.Substring(FirstComponent.Length + 1); + return Child.LocateCheckLeafGroup(ChildName, out LeafName); + } + + public override int CountChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountChecks; }); + } + } + + public override int CountExplicitlyDisabledChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountExplicitlyDisabledChecks; }); + } + } + + public override int CountExplicitlyEnabledChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountExplicitlyEnabledChecks; }); + } + } + public override int CountInheritedChecks + { + get + { + return Children_.Aggregate(0, (X, V) => { return X + V.Value.CountInheritedChecks; }); + } + } + + public IDictionary Children + { + get { return Children_; } + } + } + + public class CheckLeaf : CheckTreeNode + { + private DynamicPropertyDescriptor Property_; + + public CheckLeaf(string Name, CheckTree Parent, DynamicPropertyDescriptor Property) + : base(Name, Parent) + { + Property_ = Property; + } + + public override int CountChecks + { + get + { + return 1; + } + } + + public override int CountExplicitlyDisabledChecks + { + get + { + if (Property_.IsInheriting) + return 0; + return (bool)Property_.GetValue(null) ? 0 : 1; + } + } + + public override int CountExplicitlyEnabledChecks + { + get + { + if (Property_.IsInheriting) + return 0; + return (bool)Property_.GetValue(null) ? 1 : 0; + } + } + + public override int CountInheritedChecks + { + get + { + return (Property_.IsInheriting) ? 1 : 0; + } + } + + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidy.csproj b/clang-tidy-vs/ClangTidy/ClangTidy.csproj new file mode 100644 index 000000000..bf74717c6 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidy.csproj @@ -0,0 +1,267 @@ + + + + + Debug + AnyCPU + 2.0 + {BE261DA1-36C6-449A-95C5-4653A549170A} + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + LLVM.ClangTidy + ClangTidy + true + Key.snk + v4.5 + 14.0 + + + + + 4.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 0 + false + AnyCPU + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + false + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\YamlDotNet.3.3.0\lib\net35\YamlDotNet.dll + True + + + ..\packages\YamlDotNet.Dynamic.3.2.3\lib\net40\YamlDotNet.Dynamic.dll + True + + + + + {80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2} + 8 + 0 + 0 + primary + False + False + + + {26AD1324-4B7C-44BC-84F8-B86AED45729F} + 10 + 0 + 0 + primary + False + False + + + {1A31287A-4D7D-413E-8E32-3B374931BD89} + 8 + 0 + 0 + primary + False + False + + + {2CE2370E-D744-4936-A090-3FFFE667B0E1} + 9 + 0 + 0 + primary + False + False + + + {1CBA492E-7263-47BB-87FE-639000619B15} + 8 + 0 + 0 + primary + False + False + + + {00020430-0000-0000-C000-000000000046} + 2 + 0 + 0 + primary + False + False + + + + + + + + + Component + + + Component + + + + + + + Component + + + + + True + True + Resources.resx + + + + + + + UserControl + + + ClangTidyPropertyGrid.cs + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ClangTidyPropertyGrid.cs + + + true + VSPackage + + + + + + + + Designer + + + + + Menus.ctmenu + Designer + + + + + + + + true + + + true + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 4.5 + true + + + + true + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + if not exist $(ProjectDir)Key.snk ("$(SDKToolsPath)\sn.exe" -k $(ProjectDir)Key.snk) + + + + + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/ClangTidy.vsct b/clang-tidy-vs/ClangTidy/ClangTidy.vsct new file mode 100644 index 000000000..8bdaeec74 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidy.vsct @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs b/clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs new file mode 100644 index 000000000..59234d74b --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyCheckAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + public class ClangTidyCheckAttribute : Attribute + { + private string CheckName_; + public ClangTidyCheckAttribute(string CheckName) + { + this.CheckName_ = CheckName; + } + + public string CheckName + { + get { return CheckName_; } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs b/clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs new file mode 100644 index 000000000..db5b055c7 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyConfigParser.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace LLVM.ClangTidy +{ + static class ClangTidyConfigParser + { + public class CheckOption + { + [YamlAlias("key")] + public string Key { get; set; } + + [YamlAlias("value")] + public string Value { get; set; } + } + public class ClangTidyYaml + { + [YamlAlias("Checks")] + public string Checks { get; set; } + + [YamlAlias("CheckOptions")] + public List CheckOptions { get; set; } + } + + public static List> ParseConfigurationChain(string ClangTidyFile) + { + List> Result = new List>(); + Result.Add(new KeyValuePair(null, ClangTidyProperties.RootProperties)); + + foreach (string P in Utility.SplitPath(ClangTidyFile).Reverse()) + { + if (!Utility.HasClangTidyFile(P)) + continue; + + string ConfigFile = Path.Combine(P, ".clang-tidy"); + + using (StreamReader Reader = new StreamReader(ConfigFile)) + { + Deserializer D = new Deserializer(namingConvention: new PascalCaseNamingConvention()); + ClangTidyYaml Y = D.Deserialize(Reader); + ClangTidyProperties Parent = Result[Result.Count - 1].Value; + ClangTidyProperties NewProps = new ClangTidyProperties(Parent); + SetPropertiesFromYaml(Y, NewProps); + Result.Add(new KeyValuePair(P, NewProps)); + } + } + return Result; + } + + enum TreeLevelOp + { + Enable, + Disable, + Inherit + } + + public static void SerializeClangTidyFile(ClangTidyProperties Props, string ClangTidyFilePath) + { + List CommandList = new List(); + SerializeCheckTree(CommandList, Props.GetCheckTree(), TreeLevelOp.Inherit); + + CommandList.Sort((x, y) => + { + bool LeftSub = x.StartsWith("-"); + bool RightSub = y.StartsWith("-"); + if (LeftSub && !RightSub) + return -1; + if (RightSub && !LeftSub) + return 1; + return StringComparer.CurrentCulture.Compare(x, y); + }); + + string ConfigFile = Path.Combine(ClangTidyFilePath, ".clang-tidy"); + using (StreamWriter Writer = new StreamWriter(ConfigFile)) + { + Serializer S = new Serializer(namingConvention: new PascalCaseNamingConvention()); + ClangTidyYaml Yaml = new ClangTidyYaml(); + Yaml.Checks = String.Join(",", CommandList.ToArray()); + S.Serialize(Writer, Yaml); + } + } + + /// + /// Convert the given check tree into serialized list of commands that can be written to + /// the Yaml. The goal here is to determine the minimal sequence of check commands that + /// will produce the exact configuration displayed in the UI. This is complicated by the + /// fact that an inherited True is not the same as an explicitly specified True. If the + /// user has chosen to inherit a setting in a .clang-tidy file, then changing it in the + /// parent should show the reflected changes in the current file as well. So we cannot + /// simply -* everything and then add in the checks we need, because -* immediately marks + /// every single check as explicitly false, thus disabling inheritance. + /// + /// State passed through this recursive algorithm representing + /// the sequence of commands we have determined so far. + /// + /// The check tree to serialize. This is the parameter that will be + /// recursed on as successive subtrees get serialized to `CommandList`. + /// + /// The current state of the subtree. For example, if the + /// algorithm decides to -* an entire subtree and then add back one single check, + /// after adding a -subtree-* command to CommandList, it would pass in a value of + /// CurrentOp=TreeLevelOp.Disable when it recurses down. This allows deeper iterations + /// of the algorithm to know what kind of command (if any) needs to be added to CommandList + /// in order to put a particular check into a particular state. + /// + private static void SerializeCheckTree(List CommandList, CheckTree Tree, TreeLevelOp CurrentOp) + { + int NumChecks = Tree.CountChecks; + int NumDisabled = Tree.CountExplicitlyDisabledChecks; + int NumEnabled = Tree.CountExplicitlyEnabledChecks; + int NumInherited = Tree.CountInheritedChecks; + + if (NumChecks == 0) + return; + + if (NumInherited > 0) + System.Diagnostics.Debug.Assert(CurrentOp == TreeLevelOp.Inherit); + + // If this entire tree is inherited, just exit, nothing about this needs to + // go in the clang-tidy file. + if (NumInherited == NumChecks) + return; + + TreeLevelOp NewOp = CurrentOp; + // If there are no inherited properties in this subtree, decide whether to + // explicitly enable or disable this subtree. Decide by looking at whether + // there is a larger proportion of disabled or enabled descendants. If + // there are more disabled items in this subtree for example, disabling the + // subtree will lead to a smaller configuration file. + if (NumInherited == 0) + { + if (NumDisabled >= NumEnabled) + NewOp = TreeLevelOp.Disable; + else + NewOp = TreeLevelOp.Enable; + } + + if (NewOp == TreeLevelOp.Disable) + { + // Only add an explicit disable command if the tree was not already disabled + // to begin with. + if (CurrentOp != TreeLevelOp.Disable) + { + string WildcardPath = "*"; + if (Tree.Path != null) + WildcardPath = Tree.Path + "-" + WildcardPath; + CommandList.Add("-" + WildcardPath); + } + // If the entire subtree was disabled, there's no point descending. + if (NumDisabled == NumChecks) + return; + } + else if (NewOp == TreeLevelOp.Enable) + { + // Only add an explicit enable command if the tree was not already enabled + // to begin with. Note that if we're at the root, all checks are already + // enabled by default, so there's no need to explicitly include * + if (CurrentOp != TreeLevelOp.Enable && Tree.Path != null) + { + string WildcardPath = Tree.Path + "-*"; + CommandList.Add(WildcardPath); + } + // If the entire subtree was enabled, there's no point descending. + if (NumEnabled == NumChecks) + return; + } + + foreach (var Child in Tree.Children) + { + if (Child.Value is CheckLeaf) + { + CheckLeaf Leaf = (CheckLeaf)Child.Value; + if (Leaf.CountExplicitlyEnabledChecks == 1 && NewOp != TreeLevelOp.Enable) + CommandList.Add(Leaf.Path); + else if (Leaf.CountExplicitlyDisabledChecks == 1 && NewOp != TreeLevelOp.Disable) + CommandList.Add("-" + Leaf.Path); + continue; + } + + System.Diagnostics.Debug.Assert(Child.Value is CheckTree); + CheckTree ChildTree = (CheckTree)Child.Value; + SerializeCheckTree(CommandList, ChildTree, NewOp); + } + } + + private static void SetPropertiesFromYaml(ClangTidyYaml Yaml, ClangTidyProperties Props) + { + string[] CheckCommands = Yaml.Checks.Split(','); + foreach (string Command in CheckCommands) + { + if (Command == null || Command.Length == 0) + continue; + bool Add = true; + string Pattern = Command; + if (Pattern[0] == '-') + { + Pattern = Pattern.Substring(1); + Add = false; + } + + foreach (var Match in CheckDatabase.Checks.Where(x => Utility.MatchWildcardString(x.Name, Pattern))) + { + Props.SetDynamicValue(Match.Name, Add); + } + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs b/clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs new file mode 100644 index 000000000..0f7a8e90e --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyConfigurationPage.cs @@ -0,0 +1,61 @@ +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LLVM.ClangTidy +{ + [ClassInterface(ClassInterfaceType.AutoDual)] + [CLSCompliant(false), ComVisible(true)] + public class ClangTidyConfigurationPage : DialogPage + { + ClangTidyPropertyGrid Grid = null; + protected override IWin32Window Window + { + get + { + if (Grid == null) + Grid = new ClangTidyPropertyGrid(); + return Grid; + } + } + + protected override void SaveSetting(PropertyDescriptor property) + { + base.SaveSetting(property); + } + + public override void SaveSettingsToStorage() + { + if (Grid != null) + Grid.SaveSettingsToStorage(); + + base.SaveSettingsToStorage(); + } + + public override void ResetSettings() + { + base.ResetSettings(); + } + + protected override void LoadSettingFromStorage(PropertyDescriptor prop) + { + base.LoadSettingFromStorage(prop); + } + + public override void LoadSettingsFromStorage() + { + if (Grid != null) + Grid.InitializeSettings(); + base.LoadSettingsFromStorage(); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPackage.cs b/clang-tidy-vs/ClangTidy/ClangTidyPackage.cs new file mode 100644 index 000000000..9a0c9b67a --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPackage.cs @@ -0,0 +1,56 @@ +//===-- ClangTidyPackages.cs - VSPackage for clang-tidy ----------*- C# -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class contains a VS extension package that runs clang-tidy over a +// file in a VS text editor. +// +//===----------------------------------------------------------------------===// + +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace LLVM.ClangTidy +{ + [PackageRegistration(UseManagedResourcesOnly = true)] + [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] + [ProvideMenuResource("Menus.ctmenu", 1)] + [Guid(GuidList.guidClangTidyPkgString)] + [ProvideOptionPage(typeof(ClangTidyConfigurationPage), "LLVM/Clang", "ClangTidy", 0, 0, true)] + public sealed class ClangTidyPackage : Package + { + #region Package Members + protected override void Initialize() + { + base.Initialize(); + + var commandService = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; + if (commandService != null) + { + var menuCommandID = new CommandID(GuidList.guidClangTidyCmdSet, (int)PkgCmdIDList.cmdidClangTidy); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + } + } + #endregion + + private void MenuItemCallback(object sender, EventArgs args) + { + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyProperties.cs b/clang-tidy-vs/ClangTidy/ClangTidyProperties.cs new file mode 100644 index 000000000..c6597c192 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyProperties.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + + public class ClangTidyProperties : DynamicPropertyComponent + { + private static ClangTidyProperties RootProperties_ = null; + private CheckTree CheckTree_; + private bool HasUnsavedChanges_ = false; + + public struct CheckMapping + { + public string CheckName; + public string Property; + } + + public ClangTidyProperties() + : base(null) + { + AddClangCheckProperties(); + CheckTree_ = CheckTree.Build(this); + } + + public ClangTidyProperties(DynamicPropertyComponent Parent) + : base(Parent) + { + AddClangCheckProperties(); + CheckTree_ = CheckTree.Build(this); + } + + static ClangTidyProperties() + { + RootProperties_ = new ClangTidyProperties(null); + } + + public static ClangTidyProperties RootProperties + { + get { return RootProperties_; } + } + + private void AddClangCheckProperties() + { + // Add each check in the check database + HashSet Categories = new HashSet(); + foreach (var Check in CheckDatabase.Checks) + { + string Name = Check.Name.Replace('-', '_'); + List Attrs = new List(); + Attrs.Add(new CategoryAttribute(Check.Category)); + Attrs.Add(new DisplayNameAttribute(Check.Label)); + Attrs.Add(new DefaultValueAttribute(true)); + Attrs.Add(new DescriptionAttribute(Check.Desc)); + Attrs.Add(new ClangTidyCheckAttribute(Check.Name)); + Categories.Add(Check.Category); + AddDynamicProperty(Check.Name, Attrs.ToArray()); + } + + // Add a category verb for each unique category. + foreach (string Cat in Categories) + { + List Attrs = new List(); + Attrs.Add(new CategoryAttribute(Cat)); + Attrs.Add(new DisplayNameAttribute("(Category Verbs)")); + Attrs.Add(new TypeConverterAttribute(typeof(CategoryVerbConverter))); + Attrs.Add(new DefaultValueAttribute(CategoryVerb.None)); + AddDynamicProperty(Cat + "Verb", Attrs.ToArray()); + } + } + + public CheckTree GetCheckTree() { return CheckTree_; } + public bool GetHasUnsavedChanges() { return HasUnsavedChanges_; } + public void SetHasUnsavedChanges(bool Value) { HasUnsavedChanges_ = Value; } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs new file mode 100644 index 000000000..ce9324afa --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.Designer.cs @@ -0,0 +1,119 @@ +namespace LLVM.ClangTidy +{ + partial class ClangTidyPropertyGrid + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); + this.clangTidyProperties1 = new LLVM.ClangTidy.ClangTidyProperties(); + this.clangTidyConfigurationPage1 = new LLVM.ClangTidy.ClangTidyConfigurationPage(); + this.linkLabelPath = new System.Windows.Forms.LinkLabel(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(14, 17); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(88, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Configuration File"; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(108, 14); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(222, 20); + this.textBox1.TabIndex = 1; + // + // button1 + // + this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.button1.Location = new System.Drawing.Point(336, 14); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(78, 20); + this.button1.TabIndex = 2; + this.button1.Text = "Browse"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // propertyGrid1 + // + this.propertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.propertyGrid1.Location = new System.Drawing.Point(20, 73); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.SelectedObject = this.clangTidyProperties1; + this.propertyGrid1.Size = new System.Drawing.Size(391, 384); + this.propertyGrid1.TabIndex = 6; + this.propertyGrid1.ViewBorderColor = System.Drawing.SystemColors.ControlDarkDark; + this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.propertyGrid1_PropertyValueChanged); + // + // linkLabelPath + // + this.linkLabelPath.AutoSize = true; + this.linkLabelPath.Location = new System.Drawing.Point(29, 50); + this.linkLabelPath.Name = "linkLabelPath"; + this.linkLabelPath.Size = new System.Drawing.Size(55, 13); + this.linkLabelPath.TabIndex = 7; + this.linkLabelPath.TabStop = true; + this.linkLabelPath.Text = "linkLabel1"; + this.linkLabelPath.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabelPath_LinkClicked); + // + // ClangTidyPropertyGrid + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.linkLabelPath); + this.Controls.Add(this.propertyGrid1); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.Name = "ClangTidyPropertyGrid"; + this.Size = new System.Drawing.Size(444, 469); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.PropertyGrid propertyGrid1; + private ClangTidyProperties clangTidyProperties1; + private ClangTidyConfigurationPage clangTidyConfigurationPage1; + private System.Windows.Forms.LinkLabel linkLabelPath; + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs new file mode 100644 index 000000000..20c8a8fff --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.cs @@ -0,0 +1,208 @@ +//===-- ClangTidyPropertyGrid.cs - UI for configuring clang-tidy -*- C# -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This class contains a UserControl consisting of a .NET PropertyGrid control +// allowing configuration of checks and check options for ClangTidy. +// +//===----------------------------------------------------------------------===// +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.IO; +using Microsoft.VisualStudio.Shell; + +namespace LLVM.ClangTidy +{ + /// + /// A UserControl displaying a PropertyGrid allowing configuration of clang-tidy + /// checks and check options, as well as serialization and deserialization of + /// clang-tidy configuration files. When a configuration file is loaded, the + /// entire chain of configuration files is analyzed based on the file path, + /// and quick access is provided to edit or view any of the files in the + /// configuration chain, allowing easy visualization of where values come from + /// (similar in spirit to the -explain-config option of clang-tidy). + /// + public partial class ClangTidyPropertyGrid : UserControl + { + /// + /// The sequence of .clang-tidy configuration files, starting from the root + /// of the filesystem, down to the selected file. + /// + List> PropertyChain_ = null; + + public ClangTidyPropertyGrid() + { + InitializeComponent(); + InitializeSettings(); + } + + private enum ShouldCancel + { + Yes, + No, + } + + public void SaveSettingsToStorage() + { + PersistUnsavedChanges(false); + } + + private ShouldCancel PersistUnsavedChanges(bool PromptFirst) + { + var UnsavedResults = PropertyChain_.Where(x => x.Key != null && x.Value.GetHasUnsavedChanges()); + if (UnsavedResults.Count() == 0) + return ShouldCancel.No; + + bool ShouldSave = false; + if (PromptFirst) + { + var Response = MessageBox.Show( + "You have unsaved changes! Do you want to save before loading a new file?", + "clang-tidy", + MessageBoxButtons.YesNoCancel); + + ShouldSave = (Response == DialogResult.Yes); + if (Response == DialogResult.Cancel) + return ShouldCancel.Yes; + } + else + ShouldSave = true; + + if (ShouldSave) + { + foreach (var Result in UnsavedResults) + { + ClangTidyConfigParser.SerializeClangTidyFile(Result.Value, Result.Key); + Result.Value.SetHasUnsavedChanges(false); + } + } + return ShouldCancel.No; + } + + public void InitializeSettings() + { + PropertyChain_ = new List>(); + PropertyChain_.Add(new KeyValuePair(null, ClangTidyProperties.RootProperties)); + reloadPropertyChain(); + } + + private void button1_Click(object sender, EventArgs e) + { + ShouldCancel Cancel = PersistUnsavedChanges(true); + if (Cancel == ShouldCancel.Yes) + return; + + using (OpenFileDialog D = new OpenFileDialog()) + { + D.Filter = "Clang Tidy files|.clang-tidy"; + D.CheckPathExists = true; + D.CheckFileExists = true; + + if (D.ShowDialog() == DialogResult.OK) + { + PropertyChain_.Clear(); + PropertyChain_ = ClangTidyConfigParser.ParseConfigurationChain(D.FileName); + textBox1.Text = D.FileName; + reloadPropertyChain(); + } + } + } + + private static readonly string DefaultText = "(Default)"; + private static readonly string BrowseText = "Browse for a file to edit its properties"; + + /// + /// After a new configuration file is chosen, analyzes the directory hierarchy + /// and finds all .clang-tidy files in the path, parses them and updates the + /// PropertyGrid and quick-access LinkLabel control to reflect the new property + /// chain. + /// + private void reloadPropertyChain() + { + StringBuilder LinkBuilder = new StringBuilder(); + LinkBuilder.Append(DefaultText); + LinkBuilder.Append(" > "); + int PrefixLength = LinkBuilder.Length; + + if (PropertyChain_.Count == 1) + LinkBuilder.Append(BrowseText); + else + LinkBuilder.Append(PropertyChain_[PropertyChain_.Count - 1].Key); + + linkLabelPath.Text = LinkBuilder.ToString(); + + // Given a path like D:\Foo\Bar\Baz, construct a LinkLabel where individual + // components of the path are clickable iff they contain a .clang-tidy file. + // Clicking one of the links then updates the PropertyGrid to display the + // selected .clang-tidy file. + ClangTidyProperties LastProps = ClangTidyProperties.RootProperties; + linkLabelPath.Links.Clear(); + linkLabelPath.Links.Add(0, DefaultText.Length, LastProps); + foreach (var Prop in PropertyChain_.Skip(1)) + { + LastProps = Prop.Value; + string ClangTidyFolder = Path.GetFileName(Prop.Key); + int ClangTidyFolderOffset = Prop.Key.Length - ClangTidyFolder.Length; + linkLabelPath.Links.Add(PrefixLength + ClangTidyFolderOffset, ClangTidyFolder.Length, LastProps); + } + propertyGrid1.SelectedObject = LastProps; + } + + private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) + { + ClangTidyProperties Props = (ClangTidyProperties)propertyGrid1.SelectedObject; + Props.SetHasUnsavedChanges(true); + + // When a CategoryVerb is selected, perform the corresponding action. + PropertyDescriptor Property = e.ChangedItem.PropertyDescriptor; + if (!(e.ChangedItem.Value is CategoryVerb)) + return; + + CategoryVerb Action = (CategoryVerb)e.ChangedItem.Value; + if (Action == CategoryVerb.None) + return; + + var Category = Property.Attributes.OfType().FirstOrDefault(); + if (Category == null) + return; + var SameCategoryProps = Props.GetProperties(new Attribute[] { Category }); + foreach (PropertyDescriptor P in SameCategoryProps) + { + if (P == Property) + continue; + switch (Action) + { + case CategoryVerb.Disable: + P.SetValue(propertyGrid1.SelectedObject, false); + break; + case CategoryVerb.Enable: + P.SetValue(propertyGrid1.SelectedObject, true); + break; + case CategoryVerb.Inherit: + P.ResetValue(propertyGrid1.SelectedObject); + break; + } + } + Property.ResetValue(propertyGrid1.SelectedObject); + propertyGrid1.Invalidate(); + } + + private void linkLabelPath_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + ClangTidyProperties Props = (ClangTidyProperties)e.Link.LinkData; + propertyGrid1.SelectedObject = Props; + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx new file mode 100644 index 000000000..22dfde014 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ClangTidyPropertyGrid.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 183, 17 + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs new file mode 100644 index 000000000..099f1c0a3 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.Designer.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + partial class DynamicPropertyComponent + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs new file mode 100644 index 000000000..e8843db40 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyComponent.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + /// + /// The goal of this class is to enable displaying of a PropertyGrid in much the + /// same way that Visual Studio's C++ project system does. A project or file can + /// have properties which might inherit from their parent, or be overridden. + /// It turns out this is somewhat non-trivial. The .NET PropertyGrid is good makes + /// displaying simple properties with a static notion of what constitutes a + /// "default" value very easy. You simply apply an Attribute to the class that says + /// what the default value is and you're done. But when you try to introduce the idea + /// that a property's default value depends on some other factor, things get much more + /// complicated due to the static nature of Attributes. + /// + /// The solution to this is to inherit from ICustomTypeDescriptor. This is the mechanism + /// by which you can inject or modify attributes or properties at runtime. The .NET + /// PropertyGrid is designed in such a way that instead of using simple .NET Reflection to + /// look for the properties and attributes on a class, it will invoke the methods of + /// ICustomTypeDescriptor (if your type inherits from it), and ask those methods. Our + /// implementation of ICustomTypeDescriptor works by waiting until the PropertyGrid requests + /// PropertyDescriptors for each of the properties, and then "decorating" them with our + /// own custom PropertyDescriptor implementation which understands the proeprty inheritance + /// model we wish to implement. + /// + public partial class DynamicPropertyComponent : Component, ICustomTypeDescriptor + { + PropertyDescriptorCollection DynamicProperties_ = new PropertyDescriptorCollection(null); + private DynamicPropertyComponent Parent_; + + public DynamicPropertyComponent(DynamicPropertyComponent Parent) + { + Parent_ = Parent; + } + + public DynamicPropertyComponent(DynamicPropertyComponent Parent, IContainer container) + { + Parent_ = Parent; + + container.Add(this); + InitializeComponent(); + } + + public AttributeCollection GetAttributes() + { + return TypeDescriptor.GetAttributes(GetType()); + } + + public string GetClassName() + { + return TypeDescriptor.GetClassName(GetType()); + } + + public string GetComponentName() + { + return TypeDescriptor.GetComponentName(GetType()); + } + + public TypeConverter GetConverter() + { + return TypeDescriptor.GetConverter(GetType()); + } + + public EventDescriptor GetDefaultEvent() + { + return TypeDescriptor.GetDefaultEvent(GetType()); + } + + public PropertyDescriptor GetDefaultProperty() + { + return TypeDescriptor.GetDefaultProperty(GetType()); + } + + public object GetEditor(Type editorBaseType) + { + return TypeDescriptor.GetEditor(GetType(), editorBaseType); + } + + public EventDescriptorCollection GetEvents() + { + return TypeDescriptor.GetEvents(GetType()); + } + + public EventDescriptorCollection GetEvents(Attribute[] attributes) + { + return TypeDescriptor.GetEvents(GetType(), attributes); + } + + public PropertyDescriptorCollection GetProperties() + { + return DynamicProperties_; + } + + public PropertyDescriptorCollection GetProperties(Attribute[] attributes) + { + var Props = DynamicProperties_.OfType(); + var Filtered = Props.Where(x => x.Attributes.Contains(attributes)).ToArray(); + return new PropertyDescriptorCollection(Filtered); + } + + public object GetPropertyOwner(PropertyDescriptor pd) + { + return this; + } + + public void SetDynamicValue(string Name, T Value) + { + Name = Name.Replace('-', '_'); + DynamicPropertyDescriptor Descriptor = (DynamicPropertyDescriptor)DynamicProperties_.Find(Name, false); + Descriptor.SetValue(this, Value); + } + + public T GetDynamicValue(string Name) + { + Name = Name.Replace('-', '_'); + DynamicPropertyDescriptor Descriptor = (DynamicPropertyDescriptor)DynamicProperties_.Find(Name, false); + return (T)Descriptor.GetValue(this); + } + + protected void AddDynamicProperty(string Name, Attribute[] Attributes) + { + Name = Name.Replace('-', '_'); + + // If we have a parent, find the corresponding PropertyDescriptor with the same + // name from the parent. + DynamicPropertyDescriptor ParentDescriptor = null; + if (Parent_ != null) + ParentDescriptor = (DynamicPropertyDescriptor)Parent_.GetProperties().Find(Name, false); + + DynamicProperties_.Add(new DynamicPropertyDescriptor(Name, ParentDescriptor, Name, Attributes)); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs new file mode 100644 index 000000000..9442667dd --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyConverter.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + class MagicInheritance + { + public static readonly string Value = "{3A27184D-1774-489B-9BB7-7191B8E8E622}"; + public static readonly string Text = ""; + } + + + class DynamicPropertyConverter : TypeConverter + { + private DynamicPropertyDescriptor Descriptor_; + private TypeConverter Root_; + + public DynamicPropertyConverter(DynamicPropertyDescriptor Descriptor, TypeConverter Root) + { + Descriptor_ = Descriptor; + Root_ = Root; + } + + /// + /// Returns true if there are specific values that can be chosen from a dropdown + /// for this property. Regardless of whether standard values are supported for + /// the underlying type, we always support standard values because we need to + /// display the inheritance option. + /// + /// true + public override bool GetStandardValuesSupported(ITypeDescriptorContext context) + { + return true; + } + + /// + /// Get the set of all standard values that can be chosen from a dropdown for this + /// property. If the underlying type supports standard values, we want to include + /// all those. Additionally, we want to display the option to inherit the value, + /// but only if the value is not already inheriting. + /// + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + { + List Values = new List(); + if (Root_.GetStandardValuesSupported(context)) + { + StandardValuesCollection RootValues = Root_.GetStandardValues(context); + Values.AddRange(RootValues.Cast()); + } + if (!Descriptor_.IsInheriting) + Values.Add(MagicInheritance.Value); + StandardValuesCollection Result = new StandardValuesCollection(Values); + return Result; + } + + /// + /// Determines whether this property can accept values other than those specified + /// in the dropdown (for example by manually typing into the field). + /// + public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) + { + // Although we add items to the dropdown list, we do not change whether or not + // the set of values are exclusive. If the user could type into the field before + // they still can. And if they couldn't before, they still can't. + return Root_.GetStandardValuesExclusive(context); + } + + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return Root_.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return Root_.CanConvertTo(context, destinationType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value.Equals(MagicInheritance.Value)) + return MagicInheritance.Text; + return Root_.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (value.GetType() == destinationType) + return value; + + return Root_.ConvertTo(context, culture, value, destinationType); + } + + public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) + { + return Root_.CreateInstance(context, propertyValues); + } + + public override bool Equals(object obj) + { + return Root_.Equals(obj); + } + + public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) + { + return Root_.GetCreateInstanceSupported(context); + } + + public override int GetHashCode() + { + return Root_.GetHashCode(); + } + + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) + { + return Root_.GetProperties(context, value, attributes); + } + + public override bool GetPropertiesSupported(ITypeDescriptorContext context) + { + return Root_.GetPropertiesSupported(context); + } + + public override bool IsValid(ITypeDescriptorContext context, object value) + { + return Root_.IsValid(context, value); + } + + public override string ToString() + { + return Root_.ToString(); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs b/clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs new file mode 100644 index 000000000..f0d76914d --- /dev/null +++ b/clang-tidy-vs/ClangTidy/DynamicPropertyDescriptor.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + public class DynamicPropertyDescriptor : PropertyDescriptor + { + T Value_; + DynamicPropertyDescriptor Parent_; + bool IsInheriting_; + object Component_; + + public DynamicPropertyDescriptor(object Component, DynamicPropertyDescriptor Parent, string Name, Attribute[] Attrs) + : base(Name, Attrs) + { + foreach (DefaultValueAttribute Attr in Attrs.OfType()) + { + Value_ = (T)Attr.Value; + } + Parent_ = Parent; + IsInheriting_ = true; + Component_ = Component; + } + + public bool IsInheriting { get { return IsInheriting_; } set { IsInheriting_ = value; } } + public DynamicPropertyDescriptor Parent { get { return Parent_; } } + + /// + /// Determines whether this property's value should be considered "default" (e.g. + /// displayed in bold in the property grid). Root properties are unmodifiable and + /// always default. Non-root properties are default iff they are inheriting. + /// That is to say, if a property is explicitly set to False, the property should + /// be serialized even if the parent is also False. It would only not be serialized + /// if the user had explicitly chosen to inherit it. + /// + /// + /// + public override bool ShouldSerializeValue(object component) + { + return (Parent_ != null) && !IsInheriting; + } + + /// + /// Set the value back to the default. For root properties, this essentially does + /// nothing as they are read-only anyway. For non-root properties, this only means + /// that the property is now inheriting. + /// + /// + public override void ResetValue(object component) + { + IsInheriting_ = true; + } + + public override void SetValue(object component, object value) + { + // This is a bit of a trick. If the user chose the inheritance option from the + // dropdown, we will try to set the value to that string. So look for that and + // then just reset the value. + if (value.Equals(MagicInheritance.Text)) + ResetValue(component); + else + { + // By explicitly setting the value, this property is no longer inheriting, + // even if the value the property is being set to is the same as that of + // the parent. + IsInheriting_ = false; + Value_ = (T)value; + } + } + + public override TypeConverter Converter + { + get + { + // We need to return a DynamicPropertyConverter<> that can deal with our requirement + // to inject the inherit property option into the dropdown. But we still need to use + // the "real" converter to do the actual work for the underlying type. Therefore, + // we need to look for a TypeConverter<> attribute on the property, and if it is present + // forward an instance of that converter to the DynamicPropertyConverter<>. Otherwise, + // forward an instance of the default converter for type T to the DynamicPropertyConverter<>. + TypeConverter UnderlyingConverter = null; + var ConverterAttr = this.Attributes.OfType().LastOrDefault(); + if (ConverterAttr != null) + { + Type ConverterType = Type.GetType(ConverterAttr.ConverterTypeName); + UnderlyingConverter = (TypeConverter)Activator.CreateInstance(ConverterType); + } + else + UnderlyingConverter = TypeDescriptor.GetConverter(typeof(T)); + + return new DynamicPropertyConverter(this, UnderlyingConverter); + } + } + + public override bool IsReadOnly + { + get + { + return (Parent_ == null); + } + } + + public override Type ComponentType + { + get + { + return Component_.GetType(); + } + } + + public override object GetValue(object component) + { + // Return either this property's value or the parents value, depending on + // whether or not this property is inheriting. + if (IsInheriting_ && Parent != null) + return Parent.GetValue(component); + return Value_; + } + + public override bool CanResetValue(object component) + { + return !IsReadOnly; + } + + public override Type PropertyType + { + get + { + return typeof(T); + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs b/clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs new file mode 100644 index 000000000..22b08fbb5 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/ForwardingPropertyDescriptor.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LLVM.ClangTidy +{ + /// + /// A decorator of sorts. Accepts a PropertyDescriptor to its constructor + /// and forwards all calls to the underlying PropertyDescriptor. In this way + /// we can inherit from ForwardingPropertyDescriptor and override only the + /// few methods we need to customize the behavior of, while allowing the + /// underlying PropertyDescriptor to do the real work. + /// + public abstract class ForwardingPropertyDescriptor : PropertyDescriptor + { + private readonly PropertyDescriptor root; + protected PropertyDescriptor Root { get { return root; } } + protected ForwardingPropertyDescriptor(PropertyDescriptor root) + : base(root) + { + this.root = root; + } + + public override void AddValueChanged(object component, EventHandler handler) + { + root.AddValueChanged(component, handler); + } + + public override AttributeCollection Attributes + { + get + { + return root.Attributes; + } + } + + public override bool CanResetValue(object component) + { + return root.CanResetValue(component); + } + + public override string Category + { + get + { + return root.Category; + } + } + + public override Type ComponentType + { + get + { + return root.ComponentType; + } + } + + public override TypeConverter Converter + { + get + { + return root.Converter; + } + } + + public override string Description + { + get + { + return root.Description; + } + } + + public override bool DesignTimeOnly + { + get + { + return root.DesignTimeOnly; + } + } + + public override string DisplayName + { + get + { + return root.DisplayName; + } + } + + public override bool Equals(object obj) + { + return root.Equals(obj); + } + + public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter) + { + return root.GetChildProperties(instance, filter); + } + + public override object GetEditor(Type editorBaseType) + { + return root.GetEditor(editorBaseType); + } + + public override int GetHashCode() + { + return root.GetHashCode(); + } + + public override object GetValue(object component) + { + return root.GetValue(component); + } + + public override bool IsBrowsable + { + get + { + return root.IsBrowsable; + } + } + + public override bool IsLocalizable + { + get + { + return root.IsLocalizable; + } + } + + public override bool IsReadOnly + { + get + { + return root.IsReadOnly; + } + } + + public override string Name + { + get + { + return root.Name; + } + } + + public override Type PropertyType + { + get + { + return root.PropertyType; + } + } + + public override void RemoveValueChanged(object component, EventHandler handler) + { + root.RemoveValueChanged(component, handler); + } + + public override void ResetValue(object component) + { + root.ResetValue(component); + } + + public override void SetValue(object component, object value) + { + root.SetValue(component, value); + } + + public override bool ShouldSerializeValue(object component) + { + return root.ShouldSerializeValue(component); + } + + public override bool SupportsChangeEvents + { + get + { + return root.SupportsChangeEvents; + } + } + + public override string ToString() + { + return root.ToString(); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/GlobalSuppressions.cs b/clang-tidy-vs/ClangTidy/GlobalSuppressions.cs new file mode 100644 index 000000000..175a74e29 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. Project-level +// suppressions either have no target or are given a specific target +// and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click "In Project +// Suppression File". You do not need to add suppressions to this +// file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")] diff --git a/clang-tidy-vs/ClangTidy/Guids.cs b/clang-tidy-vs/ClangTidy/Guids.cs new file mode 100644 index 000000000..0c99a6f9f --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Guids.cs @@ -0,0 +1,12 @@ +using System; + +namespace LLVM.ClangTidy +{ + static class GuidList + { + public const string guidClangTidyPkgString = "AE4956BE-3DB8-430E-BBAB-7E2E9A014E9C"; + public const string guidClangTidyCmdSetString = "9E0F0493-6493-46DE-AEE1-ACD8F60F265E"; + + public static readonly Guid guidClangTidyCmdSet = new Guid(guidClangTidyCmdSetString); + }; +} diff --git a/clang-tidy-vs/ClangTidy/PkgCmdID.cs b/clang-tidy-vs/ClangTidy/PkgCmdID.cs new file mode 100644 index 000000000..3faf403a5 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/PkgCmdID.cs @@ -0,0 +1,7 @@ +namespace LLVM.ClangTidy +{ + static class PkgCmdIDList + { + public const uint cmdidClangTidy = 0x100; + }; +} \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs b/clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..710530504 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClangFormat")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("LLVM")] +[assembly: AssemblyProduct("ClangFormat")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en-US")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +// FIXME: Add a way to have this generated automatically by CMake +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/clang-tidy-vs/ClangTidy/Resources.Designer.cs b/clang-tidy-vs/ClangTidy/Resources.Designer.cs new file mode 100644 index 000000000..b601fb37f --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LLVM.ClangTidy { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LLVM.ClangTidy.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to --- + ///Checks: + ///Checks: + /// - Name: cert-dcl54-cpp + /// Label: Overloaded allocation function pairs + /// Description: Checks for violations of CERT DCL54-CPP - Overload allocation and deallocation functions as a pair in the same scope + /// Category: CERT Secure Coding Standards + /// - Name: cppcoreguidelines-interfaces-global-init + /// Label: I.22 - Complex Global Initializers + /// Description: Checks for violations of Core Guideline I.22 - Avoid complex initializers of global object [rest of string was truncated]";. + /// + internal static string ClangTidyChecks { + get { + return ResourceManager.GetString("ClangTidyChecks", resourceCulture); + } + } + } +} diff --git a/clang-tidy-vs/ClangTidy/Resources.resx b/clang-tidy-vs/ClangTidy/Resources.resx new file mode 100644 index 000000000..7f6347521 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Resources\ClangTidyChecks.yaml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml b/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml new file mode 100644 index 000000000..d35e81d96 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/Resources/ClangTidyChecks.yaml @@ -0,0 +1,321 @@ +--- +Checks: + # This file should be updated when new checks are added, and eventually we should + # generate this file automatically from the .rst files in clang-tidy. + - Category: CERT Secure Coding Standards + Label: Overloaded allocation function pairs + Description: Checks for violations of CERT DCL54-CPP - Overload allocation and deallocation functions as a pair in the same scope + Name: cert-dcl54-cpp + - Category: C++ Core Guidelines + Label: I.22 - Complex Global Initializers + Description: Checks for violations of Core Guideline I.22 - Avoid complex initializers of global objects + Name: cppcoreguidelines-interfaces-global-init + - Category: CERT Secure Coding Standards + Label: DCL50-CPP + Description: Checks for violations of CERT DCL50-CPP - Do not define a C-style variadic function + Name: cert-dcl50-cpp + - Category: C++ Core Guidelines + Label: Bounds.1 - No pointer arithmetic + Description: Checks for violations of Core Guideline Bounds.3 - Don't use pointer arithmetic. Use span<> instead. + Name: cppcoreguidelines-pro-bounds-pointer-arithmetic + - Category: C++ Core Guidelines + Label: Bounds.2 - Constant array indices + Description: Checks for violations of Core Bounds.2 - Only index into arrays using constant expressions. + Name: cppcoreguidelines-pro-bounds-constant-array-index + - Category: C++ Core Guidelines + Label: Bounds.3 - Array to Pointer Decay + Description: Checks for violations of Core Guideline Bounds.3 - No array-to-pointer decay + Name: cppcoreguidelines-pro-bounds-array-to-pointer-decay + - Category: C++ Core Guidelines + Label: const_cast (Type.3) + Description: Checks for violations of Core Guideline Type.3 - Don't use const_cast to cast away const + Name: cppcoreguidelines-pro-type-const-cast + - Category: C++ Core Guidelines + Label: C style casts (Type.4) + Description: Checks for violations of Core Guideline Type.3 - Don't use C-style (T)expression casts that would perform a static downcast, const_cast, or reinterpret_cast + Name: cppcoreguidelines-pro-type-cstyle-cast + - Category: C++ Core Guidelines + Label: reinterpret_cast (Type.1) + Description: Checks for violations of Core Guideline Type.1 - Don't use reinterpret_cast. + Name: cppcoreguidelines-pro-type-reinterpret-cast + - Category: C++ Core Guidelines + Label: Prefer dynamic_cast (Type.2) + Description: Checks for violations of Core Guideline Type.2 - Don't use static_cast downcasts. Use dynamic_cast instead. + Name: cppcoreguidelines-pro-type-static-cast-downcast + - Category: C++ Core Guidelines + Label: Member variable initialization (Type.6) + Description: Checks for violations of Core Guideline Type.6 - Always initialize a member variable. + Name: cppcoreguidelines-pro-type-member-init + - Category: C++ Core Guidelines + Label: Avoid unions (Type.7) + Description: Checks for violations of Core Guideline Type.7 - Avoid accessing members of raw unions. Use variant instead. + Name: cppcoreguidelines-pro-type-union-access + - Category: C++ Core Guidelines + Label: Don't use varargs (Type.8) + Description: Checks for violations of Core Guideline Type.8 - Avoid reading varargs or passing vararg arguments. Prefer variadic templates instead. + Name: cppcoreguidelines-pro-type-vararg + - Category: C++ Core Guidelines + Label: Don't slice (ES.63 & C.145) + Description: Checks for violations of Core Guidelines ES.63 (Don't slice) and C.145 (Access polymorphic objects through pointers and references) + Name: cppcoreguidelines-slicing + - Category: C++ Core Guidelines + Label: Detect unsafe special functions (C.21) + Description: Checks for violations of Core Guidelines C.21 - If you define or =delete any default operation, define or =delete them all. + Name: cppcoreguidelines-special-member-functions + - Category: Google Style Guide + Label: Forbid explicitly parameterized make_pair + Description: + Name: google-build-explicit-make-pair + - Category: Google Style Guide + Label: Anonymous namespace in headers + Description: + Name: google-build-namespaces + - Category: Google Style Guide + Label: Find using namespace directives + Description: + Name: google-build-using-namespace + - Category: Google Style Guide + Label: Default arguments in virtual methods + Description: + Name: google-default-arguments + - Category: Google Style Guide + Label: explicit constructors + Description: + Name: google-explicit-constructor + - Category: Google Style Guide + Label: Global namespace pollution in headers + Description: + Name: google-global-names-in-headers + - Category: Google Style Guide + Label: Braces around statements + Description: + Name: google-readability-braces-around-statements + - Category: Google Style Guide + Label: No C-style casts + Description: + Name: google-readability-casting + - Category: Google Style Guide + Label: Find large functions + Description: + Name: google-readability-function-size + - Category: Google Style Guide + Label: Namespace closing comments + Description: + Name: google-readability-namespace-comments + - Category: Google Style Guide + Label: Find unnecessary calls to .get() + Description: + Name: google-readability-redundant-smartptr-get + - Category: Google Style Guide + Label: Find noncomformant TODO comments + Description: + Name: google-readability-todo + - Category: Google Style Guide + Label: Find implementation-specific integral types + Description: + Name: google-runtime-int + - Category: Google Style Guide + Label: Find const string references + Description: + Name: google-runtime-member-string-references + - Category: Google Style Guide + Label: Find zero-length memsets + Description: + Name: google-runtime-memset + - Category: Google Style Guide + Label: Find overloads of operator& + Description: + Name: google-runtime-operator + - Category: Google Style Guide + Label: Check usage of non-const references + Description: + Name: google-runtime-references + - Category: LLVM Style Guide + Label: LLVM header guards + Description: + Name: llvm-header-guard + - Category: LLVM Style Guide + Label: LLVM include order + Description: + Name: llvm-include-order + - Category: LLVM Style Guide + Label: LLVM namespace comments + Description: + Name: llvm-namespace-comment + - Category: LLVM Style Guide + Label: Find local twines + Description: + Name: llvm-twine-local + - Category: Clang Diagnostics + Label: Warnings + Description: + Name: clang-diagnostic-warning + - Category: Clang Diagnostics + Label: Errors + Description: + Name: clang-diagnostic-error + - Category: Clang Diagnostics + Label: Unknown + Description: + Name: clang-diagnostic-unknown + - Category: Miscellaneous + Label: Validate argument comments + Description: + Name: misc-argument-comment + - Category: Miscellaneous + Label: Side effects in assert() + Description: + Name: misc-assert-side-effect + - Category: Miscellaneous + Label: bool / pointer implicit conversions + Description: + Name: misc-bool-pointer-implicit-conversion + - Category: Miscellaneous + Label: Dangling handles + Description: + Name: misc-dangling-handle + - Category: Miscellaneous + Label: Definitions in headers + Description: + Name: misc-definitions-in-headers + - Category: Miscellaneous + Label: Type mismatch in fold operations + Description: + Name: misc-fold-init-type + - Category: Miscellaneous + Label: Forward declaration namespace + Description: + Name: misc-forward-declaration-namespace + - Category: Miscellaneous + Label: Inaccurate erase + Description: + Name: misc-inaccurate-erase + - Category: Miscellaneous + Label: Incorrect rounding + Description: + Name: misc-incorrect-roundings + - Category: Miscellaneous + Label: Inefficient STL algorithms + Description: + Name: misc-inefficient-algorithm + - Category: Miscellaneous + Label: Macro parentheses + Description: + Name: misc-macro-parentheses + - Category: Miscellaneous + Label: Macro repeated side effects + Description: + Name: misc-macro-repeated-side-effects + - Category: Miscellaneous + Label: Misplaced const + Description: + Name: misc-misplaced-const + - Category: Miscellaneous + Label: Misplaced widening casts + Description: + Name: misc-misplaced-widening-cast + - Category: Miscellaneous + Label: Move constructor const arguments + Description: + Name: misc-move-const-arg + - Category: Miscellaneous + Label: Move constructor initialization + Description: + Name: misc-move-constructor-init + - Category: Miscellaneous + Label: Multi-statement macros + Description: + Name: misc-multiple-statement-macro + - Category: Miscellaneous + Label: Verify new / delete overloads + Description: + Name: misc-new-delete-overloads + - Category: Miscellaneous + Label: Ensure move constructors are noexcept + Description: + Name: misc-noexcept-move-constructor + - Category: Miscellaneous + Label: Copying of non-copyable objects + Description: + Name: misc-non-copyable-objects + - Category: Miscellaneous + Label: Find redundant expressions + Description: + Name: misc-redundant-expression + - Category: Miscellaneous + Label: sizeof() on stl containers + Description: + Name: misc-sizeof-container + - Category: Miscellaneous + Label: Suspicious sizeof() usage + Description: + Name: misc-sizeof-expression + - Category: Miscellaneous + Label: Replace assert with static_assert + Description: + Name: misc-static-assert + - Category: Miscellaneous + Label: Suspicious string constructor + Description: + Name: misc-string-constructor + - Category: Miscellaneous + Label: String integer assignment + Description: + Name: misc-string-integer-assignment + - Category: Miscellaneous + Label: String literal with embedded null + Description: + Name: misc-string-literal-with-embedded-nul + - Category: Miscellaneous + Label: Suspicious missing comma + Description: + Name: misc-suspicious-missing-comma + - Category: Miscellaneous + Label: Suspicious semicolon + Description: + Name: misc-suspicious-semicolon + - Category: Miscellaneous + Label: Suspicious string compare + Description: + Name: misc-suspicious-string-compare + - Category: Miscellaneous + Label: Swapped arguments + Description: + Name: misc-swapped-arguments + - Category: Miscellaneous + Label: Throw by value / catch by reference + Description: + Name: misc-throw-by-value-catch-by-reference + - Category: Miscellaneous + Label: Unconventional operator=() + Description: + Name: misc-unconventional-assign-operator + - Category: Miscellaneous + Label: Undelegated constructor + Description: + Name: misc-undelegated-constructor + - Category: Miscellaneous + Label: unique_ptr<> reset / release + Description: + Name: misc-uniqueptr-reset-release + - Category: Miscellaneous + Label: Unused Alias Decls + Description: + Name: misc-unused-alias-decls + - Category: Miscellaneous + Label: Unused Params + Description: + Name: misc-unused-parameters + - Category: Miscellaneous + Label: Unused Raii + Description: + Name: misc-unused-raii + - Category: Miscellaneous + Label: Unused Using Decls + Description: + Name: misc-unused-using-decls + - Category: Miscellaneous + Label: Virtual Near Miss + Description: + Name: misc-virtual-near-miss +... diff --git a/clang-tidy-vs/ClangTidy/Resources/Images_32bit.bmp b/clang-tidy-vs/ClangTidy/Resources/Images_32bit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2fa7ab009985537a36a1ae9c5e8391b24da89217 GIT binary patch literal 5176 zcmdUzc~DbF9LJ+|tkcf4j$=ngJC>OqwzcAYRjpN;)_Q?Qy^456L=jXXhoTgs0^W+K zs6eZNsHi9iR;dVzpm-smAZk!hK;)7m2qF1)f01WjHV2)tf7s0Yvim!}`}Tcici&4! zjvLfbqWcVB-(NDbW!9cqD~TohmPlH&e24d6nJ&P-0Q~PPCnx7y*X7I8Q`6EW{MYk8 z|LfOmNYhOC{932!@OAtJ-t#BIYmN<8%o+cW%8}!*63u}kvEcEJLV-jKW-lw^^k&Qk= z%vhUW*kqY>MKj!3j@ZYO@$m5QU=}mrw_xl*aQZH}xw*fs zU%x(*bi9e@OMTMwG2Yi|wXG;-ik(p3Z46W$`a{#MI~rDP#g>vXbQW?AjcA*htL-sl zd{GLqGscf9~bjDhNv5?#9;b5wMKui1_#|;Lj?^0e~?!j{--^$ABk#J3+CB4w6 zwxmGW`^LHB`*y5yy5ceIhZ>J*)(Bbc^f>DHF}EK5MuJ~=dfGB{?KKe^OKWJ`_XQ4~ z!aO1W;4waTUE8=>U>C6arH7E?TVlw^#6F)7T)EIxJ~Nc|JA-X}w(IR#K__k> z7NVD>6d-=@bOWwtV%U=sblrDEv%sQ%RSEHP^sGEuSfu$_tTWfY={$_3_0Ju#zsuXj zRK7)i&Xb12h+cPYDin$+Vn}UWU7Z`dhyj1nvyY^{uBPL97rPh9Q5#Wq`zR_hPoV5p zB#P6*kQ=iB`+euab;`)s=N)NG8QZt7*WvtCIArgRSm`YFm5%*t;H*%ey?9OYlPN79 zr*n?>t2u^y6|PLCHy=80$XevLgx!-7J>45y1&|92rBdl_@|o$~H|Z&M`OJv}C-?Z) zSLdHY&EsTLGpkZ2pgcDQ#n%oZi><{#`BXSN%&g_dFy_-bv|`(k_VXv~62wkAM^o^i z0Blw2&%u;0u%lM|yx{9Y{oGkdZ%mm+qoG4lChWuJ=f`}mPU1RE3M2XtSzRY)dJLP# z(BnsX8uL`x_J=qA3P)|pb*L(GP+ysc+R{u^7hFc!omdoJ3PoIyD*{~XZ&H0r_N*M1 z!BN;Yf}MbaW&m?n<8o>Sr0OQLX-UiHi5(vk`+sa$$>(Ks*1Nd4oCyjF+E07X6hC+N zp2+Emo4mBNbcIr>T+RA2+PgGH&z?Q=nXh;6q^HPpenSc4{YE!3(C zsLJ!$K2Bz5;SnSqSdA5Sqbm6^ct7bnX1i`i4zXM0hbMWD&|6@5`XUVW5U=O`miR?( zS1^elzuwH3r=+9=SXfw8(tS7cGdTaf=GuVM>((EgcLGCNchsl#%w0!9dWt>HyBn5# z`fyFv^FpW_8M}rTXk;ww%5(4{{{o7xg(Ga8BjO`NXtE~PUQ_~n76%}o*g330$*CmJ z;Uq~+$GqvDXdsSzk7J9uG5B0d{GxWQV-kZoeZgW*adB~5&GGYgm;@twJKDd*i?jl} z=4Y_py_24vV-OeSgO&E98=n_H{mjT* zbz_E{!0v^9C?t07+mLZS9Wrt4xil;a2tyn(%*-=<7ZBG{UwD@$eq`d`Y~tDz!5f%s zz>nr+#Z3I}X0ZMg3#Y({o}M4$I4zAsv3AT}GbFv>7e9L7lN56#BmF{b{oQK`P-Z9K zQcN%+wz*@od{$X@`bC553tF!rT%GCx5IeVRxP9@aaXtkl_=ea;#ABnNHP;FZ&Aq>z z3fY3_JsS|U%MXVG*C1l64-Rbd!tVi|2wCTbV8v4G^j?fWuZ6nb8k?5SMZi*fo%uP> zz?i?FvXFmVz8}XbZlD`2e)heX` literal 0 HcmV?d00001 diff --git a/clang-tidy-vs/ClangTidy/Resources/Package.ico b/clang-tidy-vs/ClangTidy/Resources/Package.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea3b23fe8d4b3a45be89f9286e617624007290be GIT binary patch literal 1078 zcmc&xJ8l9o5PeG`(IBx$SZ#smxB@50S<)>X?vjox?E$y~f|6^vHuyg004_Ke13iCf4sF{q z;lqOW`lIoaB$u%Ct2e&_Qe3Xk#6xfx?>a7JsvGS1C78t@vc9%2c8+y#&J#+ z%g SplitPath(string FileOrDir) + { + string P = Path.GetDirectoryName(FileOrDir); + do + { + yield return P; + P = Path.GetDirectoryName(P); + } while (P != null); + } + + public static bool HasClangTidyFile(string Folder) + { + string ClangTidy = Path.Combine(Folder, ".clang-tidy"); + return File.Exists(ClangTidy); + } + + public static bool MatchWildcardString(string Value, string Pattern) + { + string RE = Regex.Escape(Pattern).Replace(@"\*", ".*"); + return Regex.IsMatch(Value, RE); + } + } +} diff --git a/clang-tidy-vs/ClangTidy/VSPackage.resx b/clang-tidy-vs/ClangTidy/VSPackage.resx new file mode 100644 index 000000000..932b06200 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/VSPackage.resx @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ClangTidy + + + Analyzes code by calling the clang-tidy executable. + + + + Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/license.txt b/clang-tidy-vs/ClangTidy/license.txt new file mode 100644 index 000000000..b452ca2ef --- /dev/null +++ b/clang-tidy-vs/ClangTidy/license.txt @@ -0,0 +1,63 @@ +============================================================================== +LLVM Release License +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== +The LLVM software contains code written by third parties. Such software will +have its own individual LICENSE.TXT file in the directory in which it appears. +This file will describe the copyrights, license, and restrictions which apply +to that code. + +The disclaimer of warranty in the University of Illinois Open Source License +applies to all code in the LLVM Distribution, and nothing in any of the +other licenses gives permission to use the names of the LLVM Team or the +University of Illinois to endorse or promote products derived from this +Software. + +The following pieces of software have additional or alternate copyrights, +licenses, and/or restrictions: + +Program Directory +------- --------- + + diff --git a/clang-tidy-vs/ClangTidy/packages.config b/clang-tidy-vs/ClangTidy/packages.config new file mode 100644 index 000000000..75d7fafcf --- /dev/null +++ b/clang-tidy-vs/ClangTidy/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/clang-tidy-vs/ClangTidy/source.extension.vsixmanifest b/clang-tidy-vs/ClangTidy/source.extension.vsixmanifest new file mode 100644 index 000000000..cd0e86332 --- /dev/null +++ b/clang-tidy-vs/ClangTidy/source.extension.vsixmanifest @@ -0,0 +1,36 @@ + + + + ClangFormat + LLVM + 4.0.0 + A static analysis tool for C/C++ code. + 1033 + http://clang.llvm.org/extra/clang-tidy/ + license.txt + false + + + Pro + + + Pro + + + Pro + + + Pro + + + + + + + Visual Studio MPF + + + + |%CurrentProject%;PkgdefProjectOutputGroup| + + diff --git a/clang-tidy-vs/README.txt b/clang-tidy-vs/README.txt new file mode 100644 index 000000000..d8785c9fb --- /dev/null +++ b/clang-tidy-vs/README.txt @@ -0,0 +1,17 @@ +This directory contains a VSPackage project to generate a Visual Studio extension +for clang-tidy. + +Build prerequisites are: +- Visual Studio 2013 Professional +- Visual Studio 2013 SDK +- Visual Studio 2010 Professional (?) +- Visual Studio 2010 SDK (?) + +The extension is built using CMake by setting BUILD_CLANG_TIDY_VS_PLUGIN=ON +when configuring a Clang build, and building the clang_tidy_vsix target. + +The CMake build will copy clang-tidy.exe and LICENSE.TXT into the ClangTidy/ +directory so they can be bundled with the plug-in, as well as creating +ClangTidy/source.extension.vsixmanifest. Once the plug-in has been built with +CMake once, it can be built manually from the ClangTidy.sln solution in Visual +Studio. diff --git a/clang-tidy-vs/source.extension.vsixmanifest.in b/clang-tidy-vs/source.extension.vsixmanifest.in new file mode 100644 index 000000000..86ee571bd --- /dev/null +++ b/clang-tidy-vs/source.extension.vsixmanifest.in @@ -0,0 +1,36 @@ + + + + ClangTidy + LLVM + @CLANG_TIDY_VS_VERSION@ + A static analysis tool for C/C++ code. + 1033 + http://clang.llvm.org/extra/clang-tidy/ + license.txt + false + + + Pro + + + Pro + + + Pro + + + Pro + + + + + + + Visual Studio MPF + + + + |%CurrentProject%;PkgdefProjectOutputGroup| + + diff --git a/clang-tidy/CMakeLists.txt b/clang-tidy/CMakeLists.txt new file mode 100644 index 000000000..73a475c4a --- /dev/null +++ b/clang-tidy/CMakeLists.txt @@ -0,0 +1,41 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangTidy + ClangTidy.cpp + ClangTidyModule.cpp + ClangTidyDiagnosticConsumer.cpp + ClangTidyOptions.cpp + + DEPENDS + ClangSACheckers + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangRewrite + clangSema + clangStaticAnalyzerCore + clangStaticAnalyzerFrontend + clangTooling + clangToolingCore + ) + +add_subdirectory(tool) +add_subdirectory(plugin) +add_subdirectory(boost) +add_subdirectory(cert) +add_subdirectory(llvm) +add_subdirectory(cppcoreguidelines) +add_subdirectory(google) +add_subdirectory(misc) +add_subdirectory(modernize) +add_subdirectory(mpi) +add_subdirectory(performance) +add_subdirectory(readability) +add_subdirectory(utils) diff --git a/clang-tidy/ClangTidy.cpp b/clang-tidy/ClangTidy.cpp new file mode 100644 index 000000000..c39e22d41 --- /dev/null +++ b/clang-tidy/ClangTidy.cpp @@ -0,0 +1,580 @@ +//===--- tools/extra/clang-tidy/ClangTidy.cpp - Clang tidy tool -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements a clang-tidy tool. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidy.h" +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyModuleRegistry.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/ASTConsumers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Rewrite/Frontend/FixItRewriter.h" +#include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#include "clang/Tooling/DiagnosticsYaml.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry) + +namespace clang { +namespace tidy { + +namespace { +static const char *AnalyzerCheckNamePrefix = "clang-analyzer-"; + +class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer { +public: + AnalyzerDiagnosticConsumer(ClangTidyContext &Context) : Context(Context) {} + + void FlushDiagnosticsImpl(std::vector &Diags, + FilesMade *filesMade) override { + for (const ento::PathDiagnostic *PD : Diags) { + SmallString<64> CheckName(AnalyzerCheckNamePrefix); + CheckName += PD->getCheckName(); + Context.diag(CheckName, PD->getLocation().asLocation(), + PD->getShortDescription()) + << PD->path.back()->getRanges(); + + for (const auto &DiagPiece : + PD->path.flatten(/*ShouldFlattenMacros=*/true)) { + Context.diag(CheckName, DiagPiece->getLocation().asLocation(), + DiagPiece->getString(), DiagnosticIDs::Note) + << DiagPiece->getRanges(); + } + } + } + + StringRef getName() const override { return "ClangTidyDiags"; } + bool supportsLogicalOpControlFlow() const override { return true; } + bool supportsCrossFileDiagnostics() const override { return true; } + +private: + ClangTidyContext &Context; +}; + +class ErrorReporter { +public: + ErrorReporter(bool ApplyFixes, StringRef FormatStyle) + : Files(FileSystemOptions()), DiagOpts(new DiagnosticOptions()), + DiagPrinter(new TextDiagnosticPrinter(llvm::outs(), &*DiagOpts)), + Diags(IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts, + DiagPrinter), + SourceMgr(Diags, Files), ApplyFixes(ApplyFixes), TotalFixes(0), + AppliedFixes(0), WarningsAsErrors(0), FormatStyle(FormatStyle) { + 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() { + // FIXME: Run clang-format on changes. + 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(); + format::FormatStyle Style = format::getStyle("file", File, FormatStyle); + llvm::Expected CleanReplacements = + format::cleanupAroundReplacements(Code, FileAndReplacements.second, + Style); + if (!CleanReplacements) { + llvm::errs() << llvm::toString(CleanReplacements.takeError()) << "\n"; + continue; + } + if (!tooling::applyAllReplacements(CleanReplacements.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.createFileID(File, SourceLocation(), 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; + bool ApplyFixes; + unsigned TotalFixes; + unsigned AppliedFixes; + unsigned WarningsAsErrors; + StringRef FormatStyle; +}; + +class ClangTidyASTConsumer : public MultiplexConsumer { +public: + ClangTidyASTConsumer(std::vector> Consumers, + std::unique_ptr Finder, + std::vector> Checks) + : MultiplexConsumer(std::move(Consumers)), Finder(std::move(Finder)), + Checks(std::move(Checks)) {} + +private: + std::unique_ptr Finder; + std::vector> Checks; +}; + +} // namespace + +ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory( + ClangTidyContext &Context) + : Context(Context), CheckFactories(new ClangTidyCheckFactories) { + for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), + E = ClangTidyModuleRegistry::end(); + I != E; ++I) { + std::unique_ptr Module(I->instantiate()); + Module->addCheckFactories(*CheckFactories); + } +} + +static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts, + AnalyzerOptionsRef AnalyzerOptions) { + StringRef AnalyzerPrefix(AnalyzerCheckNamePrefix); + for (const auto &Opt : Opts.CheckOptions) { + StringRef OptName(Opt.first); + if (!OptName.startswith(AnalyzerPrefix)) + continue; + AnalyzerOptions->Config[OptName.substr(AnalyzerPrefix.size())] = Opt.second; + } +} + +typedef std::vector> CheckersList; + +static CheckersList getCheckersControlList(GlobList &Filter) { + CheckersList List; + + const auto &RegisteredCheckers = + AnalyzerOptions::getRegisteredCheckers(/*IncludeExperimental=*/false); + bool AnalyzerChecksEnabled = false; + for (StringRef CheckName : RegisteredCheckers) { + std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str()); + AnalyzerChecksEnabled |= Filter.contains(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") || Filter.contains(ClangTidyCheckName)) + List.emplace_back(CheckName, true); + } + return List; +} + +std::unique_ptr +ClangTidyASTConsumerFactory::CreateASTConsumer( + clang::CompilerInstance &Compiler, StringRef File) { + // FIXME: Move this to a separate method, so that CreateASTConsumer doesn't + // modify Compiler. + Context.setSourceManager(&Compiler.getSourceManager()); + Context.setCurrentFile(File); + Context.setASTContext(&Compiler.getASTContext()); + + auto WorkingDir = Compiler.getSourceManager() + .getFileManager() + .getVirtualFileSystem() + ->getCurrentWorkingDirectory(); + if (WorkingDir) + Context.setCurrentBuildDirectory(WorkingDir.get()); + + std::vector> Checks; + CheckFactories->createChecks(&Context, Checks); + + ast_matchers::MatchFinder::MatchFinderOptions FinderOptions; + if (auto *P = Context.getCheckProfileData()) + FinderOptions.CheckProfiling.emplace(P->Records); + + std::unique_ptr Finder( + new ast_matchers::MatchFinder(std::move(FinderOptions))); + + for (auto &Check : Checks) { + Check->registerMatchers(&*Finder); + Check->registerPPCallbacks(Compiler); + } + + std::vector> Consumers; + if (!Checks.empty()) + Consumers.push_back(Finder->newASTConsumer()); + + AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts(); + // FIXME: Remove this option once clang's cfg-temporary-dtors option defaults + // to true. + AnalyzerOptions->Config["cfg-temporary-dtors"] = + Context.getOptions().AnalyzeTemporaryDtors ? "true" : "false"; + + GlobList &Filter = Context.getChecksFilter(); + AnalyzerOptions->CheckersControlList = getCheckersControlList(Filter); + if (!AnalyzerOptions->CheckersControlList.empty()) { + setStaticAnalyzerCheckerOpts(Context.getOptions(), AnalyzerOptions); + AnalyzerOptions->AnalysisStoreOpt = RegionStoreModel; + AnalyzerOptions->AnalysisDiagOpt = PD_NONE; + AnalyzerOptions->AnalyzeNestedBlocks = true; + AnalyzerOptions->eagerlyAssumeBinOpBifurcation = true; + std::unique_ptr AnalysisConsumer = + ento::CreateAnalysisConsumer(Compiler); + AnalysisConsumer->AddDiagnosticConsumer( + new AnalyzerDiagnosticConsumer(Context)); + Consumers.push_back(std::move(AnalysisConsumer)); + } + return llvm::make_unique( + std::move(Consumers), std::move(Finder), std::move(Checks)); +} + +std::vector ClangTidyASTConsumerFactory::getCheckNames() { + std::vector CheckNames; + GlobList &Filter = Context.getChecksFilter(); + for (const auto &CheckFactory : *CheckFactories) { + if (Filter.contains(CheckFactory.first)) + CheckNames.push_back(CheckFactory.first); + } + + for (const auto &AnalyzerCheck : getCheckersControlList(Filter)) + CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first); + + std::sort(CheckNames.begin(), CheckNames.end()); + return CheckNames; +} + +ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() { + ClangTidyOptions::OptionMap Options; + std::vector> Checks; + CheckFactories->createChecks(&Context, Checks); + for (const auto &Check : Checks) + Check->storeOptions(Options); + return Options; +} + +DiagnosticBuilder ClangTidyCheck::diag(SourceLocation Loc, StringRef Message, + DiagnosticIDs::Level Level) { + return Context->diag(CheckName, Loc, Message, Level); +} + +void ClangTidyCheck::run(const ast_matchers::MatchFinder::MatchResult &Result) { + Context->setSourceManager(Result.SourceManager); + check(Result); +} + +OptionsView::OptionsView(StringRef CheckName, + const ClangTidyOptions::OptionMap &CheckOptions) + : NamePrefix(CheckName.str() + "."), CheckOptions(CheckOptions) {} + +std::string OptionsView::get(StringRef LocalName, StringRef Default) const { + const auto &Iter = CheckOptions.find(NamePrefix + LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + return Default; +} + +std::string OptionsView::getLocalOrGlobal(StringRef LocalName, + StringRef Default) const { + auto Iter = CheckOptions.find(NamePrefix + LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + // Fallback to global setting, if present. + Iter = CheckOptions.find(LocalName.str()); + if (Iter != CheckOptions.end()) + return Iter->second; + return Default; +} + +void OptionsView::store(ClangTidyOptions::OptionMap &Options, + StringRef LocalName, StringRef Value) const { + Options[NamePrefix + LocalName.str()] = Value; +} + +void OptionsView::store(ClangTidyOptions::OptionMap &Options, + StringRef LocalName, int64_t Value) const { + store(Options, LocalName, llvm::itostr(Value)); +} + +std::vector getCheckNames(const ClangTidyOptions &Options) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options)); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckNames(); +} + +ClangTidyOptions::OptionMap getCheckOptions(const ClangTidyOptions &Options) { + clang::tidy::ClangTidyContext Context( + llvm::make_unique(ClangTidyGlobalOptions(), + Options)); + ClangTidyASTConsumerFactory Factory(Context); + return Factory.getCheckOptions(); +} + +ClangTidyStats +runClangTidy(std::unique_ptr OptionsProvider, + const CompilationDatabase &Compilations, + ArrayRef InputFiles, + std::vector *Errors, ProfileData *Profile) { + ClangTool Tool(Compilations, InputFiles); + clang::tidy::ClangTidyContext Context(std::move(OptionsProvider)); + + // 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 = + [&Context](const CommandLineArguments &Args, StringRef Filename) { + CommandLineArguments AdjustedArgs; + for (size_t I = 0, E = Args.size(); I < E; ++I) { + if (I + 4 < Args.size() && Args[I] == "-Xclang" && + (Args[I + 1] == "-load" || Args[I + 1] == "-add-plugin" || + StringRef(Args[I + 1]).startswith("-plugin-arg-")) && + Args[I + 2] == "-Xclang") { + I += 3; + } else + AdjustedArgs.push_back(Args[I]); + } + return AdjustedArgs; + }; + + Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter); + Tool.appendArgumentsAdjuster(PluginArgumentsRemover); + if (Profile) + Context.setCheckProfileData(Profile); + + ClangTidyDiagnosticConsumer DiagConsumer(Context); + + Tool.setDiagnosticConsumer(&DiagConsumer); + + class ActionFactory : public FrontendActionFactory { + public: + ActionFactory(ClangTidyContext &Context) : ConsumerFactory(Context) {} + FrontendAction *create() override { return new Action(&ConsumerFactory); } + + private: + class Action : public ASTFrontendAction { + public: + Action(ClangTidyASTConsumerFactory *Factory) : Factory(Factory) {} + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return Factory->CreateASTConsumer(Compiler, File); + } + + private: + ClangTidyASTConsumerFactory *Factory; + }; + + ClangTidyASTConsumerFactory ConsumerFactory; + }; + + ActionFactory Factory(Context); + Tool.run(&Factory); + *Errors = Context.getErrors(); + return Context.getStats(); +} + +void handleErrors(const std::vector &Errors, bool Fix, + StringRef FormatStyle, unsigned &WarningsAsErrorsCount) { + ErrorReporter Reporter(Fix, FormatStyle); + 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 : Errors) { + 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..017864879 --- /dev/null +++ b/clang-tidy/ClangTidy.h @@ -0,0 +1,252 @@ +//===--- ClangTidy.h - clang-tidy -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H + +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace clang { + +class CompilerInstance; +namespace tooling { +class CompilationDatabase; +} + +namespace tidy { + +/// \brief Provides access to the ``ClangTidyCheck`` options via check-local +/// names. +/// +/// Methods of this class prepend ``CheckName + "."`` to translate check-local +/// option names to global option names. +class OptionsView { +public: + /// \brief Initializes the instance using \p CheckName + "." as a prefix. + OptionsView(StringRef CheckName, + const ClangTidyOptions::OptionMap &CheckOptions); + + /// \brief Read a named option from the ``Context``. + /// + /// Reads the option with the check-local name \p LocalName from the + /// ``CheckOptions``. If the corresponding key is not present, returns + /// \p Default. + std::string get(StringRef LocalName, StringRef Default) const; + + /// \brief Read a named option from the ``Context``. + /// + /// Reads the option with the check-local name \p LocalName from local or + /// global ``CheckOptions``. Gets local option first. If local is not present, + /// falls back to get global option. If global option is not present either, + /// returns Default. + std::string getLocalOrGlobal(StringRef LocalName, StringRef Default) const; + + /// \brief Read a named option from the ``Context`` and parse it as an + /// integral type ``T``. + /// + /// Reads the option with the check-local name \p LocalName from the + /// ``CheckOptions``. If the corresponding key is not present, returns + /// \p Default. + template + typename std::enable_if::value, T>::type + get(StringRef LocalName, T Default) const { + std::string Value = get(LocalName, ""); + T Result = Default; + if (!Value.empty()) + StringRef(Value).getAsInteger(10, Result); + return Result; + } + + /// \brief Read a named option from the ``Context`` and parse it as an + /// integral type ``T``. + /// + /// Reads the option with the check-local name \p LocalName from local or + /// global ``CheckOptions``. Gets local option first. If local is not present, + /// falls back to get global option. If global option is not present either, + /// returns Default. + template + typename std::enable_if::value, T>::type + getLocalOrGlobal(StringRef LocalName, T Default) const { + std::string Value = getLocalOrGlobal(LocalName, ""); + T Result = Default; + if (!Value.empty()) + StringRef(Value).getAsInteger(10, Result); + return Result; + } + + /// \brief Stores an option with the check-local name \p LocalName with string + /// value \p Value to \p Options. + void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, + StringRef Value) const; + + /// \brief Stores an option with the check-local name \p LocalName with + /// ``int64_t`` value \p Value to \p Options. + void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, + int64_t Value) const; + +private: + std::string NamePrefix; + const ClangTidyOptions::OptionMap &CheckOptions; +}; + +/// \brief Base class for all clang-tidy checks. +/// +/// To implement a ``ClangTidyCheck``, write a subclass and override some of the +/// base class's methods. E.g. to implement a check that validates namespace +/// declarations, override ``registerMatchers``: +/// +/// ~~~{.cpp} +/// void registerMatchers(ast_matchers::MatchFinder *Finder) override { +/// Finder->addMatcher(namespaceDecl().bind("namespace"), this); +/// } +/// ~~~ +/// +/// and then override ``check(const MatchResult &Result)`` to do the actual +/// check for each match. +/// +/// A new ``ClangTidyCheck`` instance is created per translation unit. +/// +/// FIXME: Figure out whether carrying information from one TU to another is +/// useful/necessary. +class ClangTidyCheck : public ast_matchers::MatchFinder::MatchCallback { +public: + /// \brief Initializes the check with \p CheckName and \p Context. + /// + /// Derived classes must implement the constructor with this signature or + /// delegate it. If a check needs to read options, it can do this in the + /// constructor using the Options.get() methods below. + ClangTidyCheck(StringRef CheckName, ClangTidyContext *Context) + : CheckName(CheckName), Context(Context), + Options(CheckName, Context->getOptions().CheckOptions) { + assert(Context != nullptr); + assert(!CheckName.empty()); + } + + /// \brief Override this to register ``PPCallbacks`` with ``Compiler``. + /// + /// This should be used for clang-tidy checks that analyze preprocessor- + /// dependent properties, e.g. the order of include directives. + virtual void registerPPCallbacks(CompilerInstance &Compiler) {} + + /// \brief Override this to register AST matchers with \p Finder. + /// + /// This should be used by clang-tidy checks that analyze code properties that + /// dependent on AST knowledge. + /// + /// You can register as many matchers as necessary with \p Finder. Usually, + /// "this" will be used as callback, but you can also specify other callback + /// classes. Thereby, different matchers can trigger different callbacks. + /// + /// If you need to merge information between the different matchers, you can + /// store these as members of the derived class. However, note that all + /// matches occur in the order of the AST traversal. + virtual void registerMatchers(ast_matchers::MatchFinder *Finder) {} + + /// \brief ``ClangTidyChecks`` that register ASTMatchers should do the actual + /// work in here. + virtual void check(const ast_matchers::MatchFinder::MatchResult &Result) {} + + /// \brief Add a diagnostic with the check's name. + DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, + DiagnosticIDs::Level Level = DiagnosticIDs::Warning); + + /// \brief Should store all options supported by this check with their + /// current values or default values for options that haven't been overridden. + /// + /// The check should use ``Options.store()`` to store each option it supports + /// whether it has the default value or it has been overridden. + virtual void storeOptions(ClangTidyOptions::OptionMap &Options) {} + +private: + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + StringRef getID() const override { return CheckName; } + std::string CheckName; + ClangTidyContext *Context; + +protected: + OptionsView Options; + /// \brief Returns the main file name of the current translation unit. + StringRef getCurrentMainFile() const { return Context->getCurrentFile(); } + /// \brief Returns the language options from the context. + LangOptions getLangOpts() const { return Context->getLangOpts(); } +}; + +class ClangTidyCheckFactories; + +class ClangTidyASTConsumerFactory { +public: + ClangTidyASTConsumerFactory(ClangTidyContext &Context); + + /// \brief Returns an ASTConsumer that runs the specified clang-tidy checks. + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, StringRef File); + + /// \brief Get the list of enabled checks. + std::vector getCheckNames(); + + /// \brief Get the union of options from all checks. + ClangTidyOptions::OptionMap getCheckOptions(); + +private: + ClangTidyContext &Context; + std::unique_ptr CheckFactories; +}; + +/// \brief Fills the list of check names that are enabled when the provided +/// filters are applied. +std::vector getCheckNames(const ClangTidyOptions &Options); + +/// \brief Returns the effective check-specific options. +/// +/// The method configures ClangTidy with the specified \p Options and collects +/// effective options from all created checks. The returned set of options +/// includes default check-specific options for all keys not overridden by \p +/// Options. +ClangTidyOptions::OptionMap getCheckOptions(const ClangTidyOptions &Options); + +/// \brief Run a set of clang-tidy checks on a set of files. +/// +/// \param Profile if provided, it enables check profile collection in +/// MatchFinder, and will contain the result of the profile. +ClangTidyStats +runClangTidy(std::unique_ptr OptionsProvider, + const tooling::CompilationDatabase &Compilations, + ArrayRef InputFiles, + std::vector *Errors, + ProfileData *Profile = nullptr); + +// FIXME: This interface will need to be significantly extended to be useful. +// FIXME: Implement confidence levels for displaying/fixing errors. +// +/// \brief Displays the found \p Errors to the users. If \p Fix is true, \p +/// Errors containing fixes are automatically applied and reformatted. If no +/// clang-format configuration file is found, the given \P FormatStyle is used. +void handleErrors(const std::vector &Errors, bool Fix, + StringRef FormatStyle, unsigned &WarningsAsErrorsCount); + +/// \brief Serializes replacements into YAML and writes them to the specified +/// output stream. +void exportReplacements(StringRef MainFilePath, + const std::vector &Errors, + raw_ostream &OS); + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDY_H diff --git a/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tidy/ClangTidyDiagnosticConsumer.cpp new file mode 100644 index 000000000..2a7ddbbfb --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -0,0 +1,588 @@ +//===--- tools/extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp ----------=== // +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements ClangTidyDiagnosticConsumer, ClangTidyContext +/// and ClangTidyError classes. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "ClangTidyDiagnosticConsumer.h" +#include "ClangTidyOptions.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Frontend/DiagnosticRenderer.h" +#include "llvm/ADT/SmallString.h" +#include +#include +using namespace clang; +using namespace tidy; + +namespace { +class ClangTidyDiagnosticRenderer : public DiagnosticRenderer { +public: + ClangTidyDiagnosticRenderer(const LangOptions &LangOpts, + DiagnosticOptions *DiagOpts, + ClangTidyError &Error) + : DiagnosticRenderer(LangOpts, DiagOpts), Error(Error) {} + +protected: + void emitDiagnosticMessage(SourceLocation Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, StringRef Message, + ArrayRef Ranges, + const SourceManager *SM, + 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, *SM, 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(SourceLocation Loc, PresumedLoc PLoc, + DiagnosticsEngine::Level Level, + ArrayRef Ranges, + const SourceManager &SM) override {} + + void emitCodeContext(SourceLocation Loc, DiagnosticsEngine::Level Level, + SmallVectorImpl &Ranges, + ArrayRef Hints, + const SourceManager &SM) 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(SM, 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(SourceLocation Loc, PresumedLoc PLoc, + const SourceManager &SM) override {} + + void emitImportLocation(SourceLocation Loc, PresumedLoc PLoc, + StringRef ModuleName, + const SourceManager &SM) override {} + + void emitBuildingModuleLocation(SourceLocation Loc, PresumedLoc PLoc, + StringRef ModuleName, + const SourceManager &SM) 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) { + 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 Glob = GlobList.substr(0, GlobList.find(',')).trim(); + GlobList = GlobList.substr(Glob.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; +} + +ClangTidyContext::ClangTidyContext( + std::unique_ptr OptionsProvider) + : DiagEngine(nullptr), OptionsProvider(std::move(OptionsProvider)), + Profile(nullptr) { + // Before the first translation unit we can get errors related to command-line + // parsing, use empty string for the file name in this case. + setCurrentFile(""); +} + +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.reset(new GlobList(*getOptions().Checks)); + WarningAsErrorFilter.reset(new GlobList(*getOptions().WarningsAsErrors)); +} + +void ClangTidyContext::setASTContext(ASTContext *Context) { + DiagEngine->SetArgToStringFn(&FormatASTNodeDiagnosticArgument, Context); + LangOpts = Context->getLangOpts(); +} + +const ClangTidyGlobalOptions &ClangTidyContext::getGlobalOptions() const { + return OptionsProvider->getGlobalOptions(); +} + +const ClangTidyOptions &ClangTidyContext::getOptions() const { + return CurrentOptions; +} + +ClangTidyOptions ClangTidyContext::getOptionsForFile(StringRef File) const { + // Merge options on top of getDefaults() as a safeguard against options with + // unset values. + return ClangTidyOptions::getDefaults().mergeWith( + OptionsProvider->getOptions(File)); +} + +void ClangTidyContext::setCheckProfileData(ProfileData *P) { Profile = P; } + +GlobList &ClangTidyContext::getChecksFilter() { + assert(CheckFilter != nullptr); + return *CheckFilter; +} + +GlobList &ClangTidyContext::getWarningAsErrorFilter() { + assert(WarningAsErrorFilter != nullptr); + return *WarningAsErrorFilter; +} + +/// \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) + : Context(Ctx), LastErrorRelatesToUserCode(false), + LastErrorPassesLineFilter(false), LastErrorWasIgnored(false) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + Diags.reset(new DiagnosticsEngine( + IntrusiveRefCntPtr(new DiagnosticIDs), &*DiagOpts, this, + /*ShouldOwnClient=*/false)); + Context.setDiagnosticsEngine(Diags.get()); +} + +void ClangTidyDiagnosticConsumer::finalizeLastError() { + if (!Errors.empty()) { + ClangTidyError &Error = Errors.back(); + if (!Context.getChecksFilter().contains(Error.DiagnosticName) && + Error.DiagLevel != ClangTidyError::Error) { + ++Context.Stats.ErrorsIgnoredCheckFilter; + Errors.pop_back(); + } else if (!LastErrorRelatesToUserCode) { + ++Context.Stats.ErrorsIgnoredNonUserCode; + Errors.pop_back(); + } else if (!LastErrorPassesLineFilter) { + ++Context.Stats.ErrorsIgnoredLineFilter; + Errors.pop_back(); + } else { + ++Context.Stats.ErrorsDisplayed; + } + } + LastErrorRelatesToUserCode = false; + LastErrorPassesLineFilter = false; +} + +static bool LineIsMarkedWithNOLINT(SourceManager &SM, SourceLocation Loc) { + bool Invalid; + const char *CharacterData = SM.getCharacterData(Loc, &Invalid); + if (!Invalid) { + const char *P = CharacterData; + while (*P != '\0' && *P != '\r' && *P != '\n') + ++P; + StringRef RestOfLine(CharacterData, P - CharacterData + 1); + // FIXME: Handle /\bNOLINT\b(\([^)]*\))?/ as cpplint.py does. + if (RestOfLine.find("NOLINT") != StringRef::npos) { + return true; + } + } + return false; +} + +static bool LineIsMarkedWithNOLINTinMacro(SourceManager &SM, + SourceLocation Loc) { + while (true) { + if (LineIsMarkedWithNOLINT(SM, Loc)) + return true; + if (!Loc.isMacroID()) + return false; + Loc = SM.getImmediateExpansionRange(Loc).first; + } + return false; +} + +void ClangTidyDiagnosticConsumer::HandleDiagnostic( + DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { + if (LastErrorWasIgnored && DiagLevel == DiagnosticsEngine::Note) + return; + + if (Info.getLocation().isValid() && DiagLevel != DiagnosticsEngine::Error && + DiagLevel != DiagnosticsEngine::Fatal && + LineIsMarkedWithNOLINTinMacro(Diags->getSourceManager(), + Info.getLocation())) { + ++Context.Stats.ErrorsIgnoredNOLINT; + // Ignored a warning, should ignore related notes as well + LastErrorWasIgnored = true; + return; + } + + LastErrorWasIgnored = false; + // Count warnings/errors. + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + if (DiagLevel == DiagnosticsEngine::Note) { + assert(!Errors.empty() && + "A diagnostic note can only be appended to a message."); + } else { + finalizeLastError(); + StringRef WarningOption = + Context.DiagEngine->getDiagnosticIDs()->getWarningOptionForDiag( + Info.getID()); + std::string CheckName = !WarningOption.empty() + ? ("clang-diagnostic-" + WarningOption).str() + : Context.getCheckName(Info.getID()).str(); + + if (CheckName.empty()) { + // This is a compiler diagnostic without a warning option. Assign check + // name based on its level. + switch (DiagLevel) { + case DiagnosticsEngine::Error: + case DiagnosticsEngine::Fatal: + CheckName = "clang-diagnostic-error"; + break; + case DiagnosticsEngine::Warning: + CheckName = "clang-diagnostic-warning"; + break; + default: + CheckName = "clang-diagnostic-unknown"; + break; + } + } + + ClangTidyError::Level Level = ClangTidyError::Warning; + if (DiagLevel == DiagnosticsEngine::Error || + DiagLevel == DiagnosticsEngine::Fatal) { + // Force reporting of Clang errors regardless of filters and non-user + // code. + Level = ClangTidyError::Error; + LastErrorRelatesToUserCode = true; + LastErrorPassesLineFilter = true; + } + bool IsWarningAsError = + DiagLevel == DiagnosticsEngine::Warning && + Context.getWarningAsErrorFilter().contains(CheckName); + Errors.emplace_back(CheckName, Level, Context.getCurrentBuildDirectory(), + IsWarningAsError); + } + + ClangTidyDiagnosticRenderer Converter( + Context.getLangOpts(), &Context.DiagEngine->getDiagnosticOptions(), + Errors.back()); + SmallString<100> Message; + Info.FormatDiagnostic(Message); + SourceManager *Sources = nullptr; + if (Info.hasSourceManager()) + Sources = &Info.getSourceManager(); + Converter.emitDiagnostic(Info.getLocation(), DiagLevel, Message, + Info.getRanges(), Info.getFixItHints(), Sources); + + 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.reset( + new llvm::Regex(*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()); + 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..eb831a180 --- /dev/null +++ b/clang-tidy/ClangTidyDiagnosticConsumer.h @@ -0,0 +1,263 @@ +//===--- ClangTidyDiagnosticConsumer.h - clang-tidy -------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H + +#include "ClangTidyOptions.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Core/Diagnostic.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/Timer.h" + +namespace clang { + +class ASTContext; +class CompilerInstance; +namespace ast_matchers { +class MatchFinder; +} +namespace tooling { +class CompilationDatabase; +} + +namespace tidy { + +/// \brief A detected error complete with information to display diagnostic and +/// automatic fix. +/// +/// This is used as an intermediate format to transport Diagnostics without a +/// dependency on a SourceManager. +/// +/// FIXME: Make Diagnostics flexible enough to support this directly. +struct ClangTidyError : tooling::Diagnostic { + ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory, + bool IsWarningAsError); + + bool IsWarningAsError; +}; + +/// \brief Read-only set of strings represented as a list of positive and +/// negative globs. Positive globs add all matched strings to the set, negative +/// globs remove them in the order of appearance in the list. +class GlobList { +public: + /// \brief \p GlobList is a comma-separated list of globs (only '*' + /// metacharacter is supported) with optional '-' prefix to denote exclusion. + GlobList(StringRef Globs); + + /// \brief Returns \c true if the pattern matches \p S. The result is the last + /// matching glob's Positive flag. + bool contains(StringRef S) { return contains(S, false); } + +private: + bool contains(StringRef S, bool Contains); + + bool Positive; + llvm::Regex Regex; + std::unique_ptr NextGlob; +}; + +/// \brief Contains displayed and ignored diagnostic counters for a ClangTidy +/// run. +struct ClangTidyStats { + ClangTidyStats() + : ErrorsDisplayed(0), ErrorsIgnoredCheckFilter(0), ErrorsIgnoredNOLINT(0), + ErrorsIgnoredNonUserCode(0), ErrorsIgnoredLineFilter(0) {} + + unsigned ErrorsDisplayed; + unsigned ErrorsIgnoredCheckFilter; + unsigned ErrorsIgnoredNOLINT; + unsigned ErrorsIgnoredNonUserCode; + unsigned ErrorsIgnoredLineFilter; + + unsigned errorsIgnored() const { + return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter + + ErrorsIgnoredNonUserCode + ErrorsIgnoredLineFilter; + } +}; + +/// \brief Container for clang-tidy profiling data. +struct ProfileData { + llvm::StringMap Records; +}; + +/// \brief Every \c ClangTidyCheck reports errors through a \c DiagnosticsEngine +/// provided by this context. +/// +/// A \c ClangTidyCheck always has access to the active context to report +/// warnings like: +/// \code +/// Context->Diag(Loc, "Single-argument constructors must be explicit") +/// << FixItHint::CreateInsertion(Loc, "explicit "); +/// \endcode +class ClangTidyContext { +public: + /// \brief Initializes \c ClangTidyContext instance. + ClangTidyContext(std::unique_ptr OptionsProvider); + + /// \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 check filter for the \c CurrentFile. + /// + /// The \c CurrentFile can be changed using \c setCurrentFile. + GlobList &getChecksFilter(); + + /// \brief Returns check filter for the \c CurrentFile which + /// selects checks for upgrade to error. + GlobList &getWarningAsErrorFilter(); + + /// \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. + const std::vector &getErrors() const { return Errors; } + + /// \brief Clears collected errors. + void clearErrors() { Errors.clear(); } + + /// \brief Set the output struct for profile data. + /// + /// Setting a non-null pointer here will enable profile collection in + /// clang-tidy. + void setCheckProfileData(ProfileData *Profile); + ProfileData *getCheckProfileData() const { return Profile; } + + /// \brief Should be called when starting to process new translation unit. + void setCurrentBuildDirectory(StringRef BuildDirectory) { + CurrentBuildDirectory = BuildDirectory; + } + + /// \brief Returns build directory of the current translation unit. + const std::string &getCurrentBuildDirectory() { + return CurrentBuildDirectory; + } + +private: + // Calls setDiagnosticsEngine() and storeError(). + friend class ClangTidyDiagnosticConsumer; + friend class ClangTidyPluginAction; + + /// \brief Sets the \c DiagnosticsEngine so that Diagnostics can be generated + /// correctly. + void setDiagnosticsEngine(DiagnosticsEngine *Engine); + + /// \brief Store an \p Error. + void storeError(const ClangTidyError &Error); + + std::vector Errors; + DiagnosticsEngine *DiagEngine; + std::unique_ptr OptionsProvider; + + std::string CurrentFile; + ClangTidyOptions CurrentOptions; + std::unique_ptr CheckFilter; + std::unique_ptr WarningAsErrorFilter; + + LangOptions LangOpts; + + ClangTidyStats Stats; + + std::string CurrentBuildDirectory; + + llvm::DenseMap CheckNamesByDiagnosticID; + + ProfileData *Profile; +}; + +/// \brief A diagnostic consumer that turns each \c Diagnostic into a +/// \c SourceManager-independent \c ClangTidyError. +// +// FIXME: If we move away from unit-tests, this can be moved to a private +// implementation file. +class ClangTidyDiagnosticConsumer : public DiagnosticConsumer { +public: + ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx); + + // 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; + 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..3a3ae82e6 --- /dev/null +++ b/clang-tidy/ClangTidyModule.cpp @@ -0,0 +1,39 @@ +//===--- 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) { + GlobList &Filter = Context->getChecksFilter(); + for (const auto &Factory : Factories) { + if (Filter.contains(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..8d7665e11 --- /dev/null +++ b/clang-tidy/ClangTidyOptions.cpp @@ -0,0 +1,339 @@ +//===--- 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) +LLVM_YAML_IS_SEQUENCE_VECTOR(ClangTidyOptions::StringPair) +LLVM_YAML_IS_SEQUENCE_VECTOR(std::string) + +namespace llvm { +namespace yaml { + +// Map std::pair to a JSON array of size 2. +template <> struct SequenceTraits { + static size_t size(IO &IO, FileFilter::LineRange &Range) { + return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2; + } + static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) { + if (Index > 1) + IO.setError("Too many elements in line range."); + return Index == 0 ? Range.first : Range.second; + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, FileFilter &File) { + IO.mapRequired("name", File.Name); + IO.mapOptional("lines", File.LineRanges); + } + static StringRef validate(IO &io, FileFilter &File) { + if (File.Name.empty()) + return "No file name specified"; + for (const FileFilter::LineRange &Range : File.LineRanges) { + if (Range.first <= 0 || Range.second <= 0) + return "Invalid line range"; + } + return StringRef(); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) { + IO.mapRequired("key", KeyValue.first); + IO.mapRequired("value", KeyValue.second); + } +}; + +struct NOptionMap { + NOptionMap(IO &) {} + NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) + : Options(OptionMap.begin(), OptionMap.end()) {} + ClangTidyOptions::OptionMap denormalize(IO &) { + ClangTidyOptions::OptionMap Map; + for (const auto &KeyValue : Options) + Map[KeyValue.first] = KeyValue.second; + return Map; + } + std::vector Options; +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, ClangTidyOptions &Options) { + MappingNormalization NOpts( + IO, Options.CheckOptions); + IO.mapOptional("Checks", Options.Checks); + IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors); + IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex); + IO.mapOptional("AnalyzeTemporaryDtors", Options.AnalyzeTemporaryDtors); + IO.mapOptional("User", Options.User); + IO.mapOptional("CheckOptions", NOpts->Options); + IO.mapOptional("ExtraArgs", Options.ExtraArgs); + IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore); + } +}; + +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace tidy { + +ClangTidyOptions ClangTidyOptions::getDefaults() { + ClangTidyOptions Options; + Options.Checks = ""; + Options.WarningsAsErrors = ""; + Options.HeaderFilterRegex = ""; + Options.SystemHeaders = false; + Options.AnalyzeTemporaryDtors = false; + Options.User = llvm::None; + for (ClangTidyModuleRegistry::iterator I = ClangTidyModuleRegistry::begin(), + E = ClangTidyModuleRegistry::end(); + I != E; ++I) + Options = Options.mergeWith(I->instantiate()->getModuleOptions()); + return Options; +} + +template +static void mergeVectors(Optional &Dest, const Optional &Src) { + if (Src) { + if (Dest) + Dest->insert(Dest->end(), Src->begin(), Src->end()); + else + Dest = Src; + } +} + +static void mergeCommaSeparatedLists(Optional &Dest, + const Optional &Src) { + if (Src) + Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src; +} + +template +static void overrideValue(Optional &Dest, const Optional &Src) { + if (Src) + Dest = Src; +} + +ClangTidyOptions +ClangTidyOptions::mergeWith(const ClangTidyOptions &Other) const { + ClangTidyOptions Result = *this; + + mergeCommaSeparatedLists(Result.Checks, Other.Checks); + mergeCommaSeparatedLists(Result.WarningsAsErrors, Other.WarningsAsErrors); + overrideValue(Result.HeaderFilterRegex, Other.HeaderFilterRegex); + overrideValue(Result.SystemHeaders, Other.SystemHeaders); + overrideValue(Result.AnalyzeTemporaryDtors, Other.AnalyzeTemporaryDtors); + overrideValue(Result.User, Other.User); + mergeVectors(Result.ExtraArgs, Other.ExtraArgs); + mergeVectors(Result.ExtraArgsBefore, Other.ExtraArgsBefore); + + for (const auto &KeyValue : Other.CheckOptions) + Result.CheckOptions[KeyValue.first] = KeyValue.second; + + return Result; +} + +const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] = + "clang-tidy binary"; +const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] = + "command-line option '-checks'"; +const char + ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] = + "command-line option '-config'"; + +ClangTidyOptions +ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) { + ClangTidyOptions Result; + for (const auto &Source : getRawOptions(FileName)) + Result = Result.mergeWith(Source.first); + return Result; +} + +std::vector +DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) { + std::vector Result; + Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary); + return Result; +} + +ConfigOptionsProvider::ConfigOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &ConfigOptions, + const ClangTidyOptions &OverrideOptions) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + ConfigOptions(ConfigOptions), OverrideOptions(OverrideOptions) {} + +std::vector +ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) { + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(FileName); + RawOptions.emplace_back(ConfigOptions, + OptionsSourceTypeConfigCommandLineOption); + RawOptions.emplace_back(OverrideOptions, + OptionsSourceTypeCheckCommandLineOption); + return RawOptions; +} + +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions) { + ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); +} + +FileOptionsProvider::FileOptionsProvider( + const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const FileOptionsProvider::ConfigFileHandlers &ConfigHandlers) + : DefaultOptionsProvider(GlobalOptions, DefaultOptions), + OverrideOptions(OverrideOptions), ConfigHandlers(ConfigHandlers) {} + +// FIXME: This method has some common logic with clang::format::getStyle(). +// Consider pulling out common bits to a findParentFileWithName function or +// similar. +std::vector +FileOptionsProvider::getRawOptions(StringRef FileName) { + DEBUG(llvm::dbgs() << "Getting options for file " << FileName << "...\n"); + + std::vector RawOptions = + DefaultOptionsProvider::getRawOptions(FileName); + OptionsSource CommandLineOptions(OverrideOptions, + OptionsSourceTypeCheckCommandLineOption); + // Look for a suitable configuration file in all parent directories of the + // file. Start with the immediate parent directory and move up. + StringRef Path = llvm::sys::path::parent_path(FileName); + for (StringRef CurrentPath = Path; !CurrentPath.empty(); + CurrentPath = llvm::sys::path::parent_path(CurrentPath)) { + llvm::Optional Result; + + auto Iter = CachedOptions.find(CurrentPath); + if (Iter != CachedOptions.end()) + Result = Iter->second; + + if (!Result) + Result = tryReadConfigFile(CurrentPath); + + if (Result) { + // Store cached value for all intermediate directories. + while (Path != CurrentPath) { + DEBUG(llvm::dbgs() << "Caching configuration for path " << Path + << ".\n"); + CachedOptions[Path] = *Result; + Path = llvm::sys::path::parent_path(Path); + } + CachedOptions[Path] = *Result; + + RawOptions.push_back(*Result); + break; + } + } + RawOptions.push_back(CommandLineOptions); + return RawOptions; +} + +llvm::Optional +FileOptionsProvider::tryReadConfigFile(StringRef Directory) { + assert(!Directory.empty()); + + if (!llvm::sys::fs::is_directory(Directory)) { + llvm::errs() << "Error reading configuration from " << Directory + << ": directory doesn't exist.\n"; + return llvm::None; + } + + for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) { + SmallString<128> ConfigFile(Directory); + llvm::sys::path::append(ConfigFile, ConfigHandler.first); + DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n"); + + bool IsFile = false; + // Ignore errors from is_regular_file: we only need to know if we can read + // the file or not. + llvm::sys::fs::is_regular_file(Twine(ConfigFile), IsFile); + if (!IsFile) + continue; + + llvm::ErrorOr> Text = + llvm::MemoryBuffer::getFile(ConfigFile.c_str()); + if (std::error_code EC = Text.getError()) { + llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message() + << "\n"; + continue; + } + + // Skip empty files, e.g. files opened for writing via shell output + // redirection. + if ((*Text)->getBuffer().empty()) + continue; + llvm::ErrorOr ParsedOptions = + ConfigHandler.second((*Text)->getBuffer()); + if (!ParsedOptions) { + if (ParsedOptions.getError()) + llvm::errs() << "Error parsing " << ConfigFile << ": " + << ParsedOptions.getError().message() << "\n"; + continue; + } + return OptionsSource(*ParsedOptions, ConfigFile.c_str()); + } + return llvm::None; +} + +/// \brief Parses -line-filter option and stores it to the \c Options. +std::error_code parseLineFilter(StringRef LineFilter, + clang::tidy::ClangTidyGlobalOptions &Options) { + llvm::yaml::Input Input(LineFilter); + Input >> Options.LineFilter; + return Input.error(); +} + +llvm::ErrorOr parseConfiguration(StringRef Config) { + llvm::yaml::Input Input(Config); + ClangTidyOptions Options; + Input >> Options; + if (Input.error()) + return Input.error(); + return Options; +} + +std::string configurationAsText(const ClangTidyOptions &Options) { + std::string Text; + llvm::raw_string_ostream Stream(Text); + llvm::yaml::Output Output(Stream); + // We use the same mapping method for input and output, so we need a non-const + // reference here. + ClangTidyOptions NonConstValue = Options; + Output << NonConstValue; + return Stream.str(); +} + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/ClangTidyOptions.h b/clang-tidy/ClangTidyOptions.h new file mode 100644 index 000000000..a3924435b --- /dev/null +++ b/clang-tidy/ClangTidyOptions.h @@ -0,0 +1,260 @@ +//===--- ClangTidyOptions.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H + +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorOr.h" +#include +#include +#include +#include +#include +#include + +namespace clang { +namespace tidy { + +/// \brief Contains a list of line ranges in a single file. +struct FileFilter { + /// \brief File name. + std::string Name; + + /// \brief LineRange is a pair (inclusive). + typedef std::pair LineRange; + + /// \brief A list of line ranges in this file, for which we show warnings. + std::vector LineRanges; +}; + +/// \brief Global options. These options are neither stored nor read from +/// configuration files. +struct ClangTidyGlobalOptions { + /// \brief Output warnings from certain line ranges of certain files only. + /// If empty, no warnings will be filtered. + std::vector LineFilter; +}; + +/// \brief Contains options for clang-tidy. These options may be read from +/// configuration files, and may be different for different translation units. +struct ClangTidyOptions { + /// \brief These options are used for all settings that haven't been + /// overridden by the \c OptionsProvider. + /// + /// Allow no checks and no headers by default. This method initializes + /// check-specific options by calling \c ClangTidyModule::getModuleOptions() + /// of each registered \c ClangTidyModule. + static ClangTidyOptions getDefaults(); + + /// \brief Creates a new \c ClangTidyOptions instance combined from all fields + /// of this instance overridden by the fields of \p Other that have a value. + ClangTidyOptions mergeWith(const ClangTidyOptions &Other) const; + + /// \brief Checks filter. + llvm::Optional Checks; + + /// \brief WarningsAsErrors filter. + llvm::Optional WarningsAsErrors; + + /// \brief Output warnings from headers matching this filter. Warnings from + /// main files will always be displayed. + llvm::Optional HeaderFilterRegex; + + /// \brief Output warnings from system headers matching \c HeaderFilterRegex. + llvm::Optional SystemHeaders; + + /// \brief Turns on temporary destructor-based analysis. + llvm::Optional AnalyzeTemporaryDtors; + + /// \brief Specifies the name or e-mail of the user running clang-tidy. + /// + /// This option is used, for example, to place the correct user name in TODO() + /// comments in the relevant check. + llvm::Optional User; + + typedef std::pair StringPair; + typedef std::map OptionMap; + + /// \brief Key-value mapping used to store check-specific options. + OptionMap CheckOptions; + + typedef std::vector ArgList; + + /// \brief Add extra compilation arguments to the end of the list. + llvm::Optional ExtraArgs; + + /// \brief Add extra compilation arguments to the start of the list. + llvm::Optional ExtraArgsBefore; +}; + +/// \brief Abstract interface for retrieving various ClangTidy options. +class ClangTidyOptionsProvider { +public: + static const char OptionsSourceTypeDefaultBinary[]; + static const char OptionsSourceTypeCheckCommandLineOption[]; + static const char OptionsSourceTypeConfigCommandLineOption[]; + + virtual ~ClangTidyOptionsProvider() {} + + /// \brief Returns global options, which are independent of the file. + virtual const ClangTidyGlobalOptions &getGlobalOptions() = 0; + + /// \brief ClangTidyOptions and its source. + // + /// clang-tidy has 3 types of the sources in order of increasing priority: + /// * clang-tidy binary. + /// * '-config' commandline option or a specific configuration file. If the + /// commandline option is specified, clang-tidy will ignore the + /// configuration file. + /// * '-checks' commandline option. + typedef std::pair OptionsSource; + + /// \brief Returns an ordered vector of OptionsSources, in order of increasing + /// priority. + virtual std::vector + getRawOptions(llvm::StringRef FileName) = 0; + + /// \brief Returns options applying to a specific translation unit with the + /// specified \p FileName. + ClangTidyOptions getOptions(llvm::StringRef FileName); +}; + +/// \brief Implementation of the \c ClangTidyOptionsProvider interface, which +/// returns the same options for all files. +class DefaultOptionsProvider : public ClangTidyOptionsProvider { +public: + DefaultOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &Options) + : GlobalOptions(GlobalOptions), DefaultOptions(Options) {} + const ClangTidyGlobalOptions &getGlobalOptions() override { + return GlobalOptions; + } + std::vector getRawOptions(llvm::StringRef FileName) override; + +private: + ClangTidyGlobalOptions GlobalOptions; + ClangTidyOptions DefaultOptions; +}; + +/// \brief Implementation of ClangTidyOptions interface, which is used for +/// '-config' command-line option. +class ConfigOptionsProvider : public DefaultOptionsProvider { +public: + ConfigOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &ConfigOptions, + const ClangTidyOptions &OverrideOptions); + std::vector getRawOptions(llvm::StringRef FileName) override; + +private: + ClangTidyOptions ConfigOptions; + ClangTidyOptions OverrideOptions; +}; + +/// \brief Implementation of the \c ClangTidyOptionsProvider interface, which +/// tries to find a configuration file in the closest parent directory of each +/// source file. +/// +/// By default, files named ".clang-tidy" will be considered, and the +/// \c clang::tidy::parseConfiguration function will be used for parsing, but a +/// custom set of configuration file names and parsing functions can be +/// specified using the appropriate constructor. +class FileOptionsProvider : public DefaultOptionsProvider { +public: + // \brief A pair of configuration file base name and a function parsing + // configuration from text in the corresponding format. + typedef std::pair( + llvm::StringRef)>> + ConfigFileHandler; + + /// \brief Configuration file handlers listed in the order of priority. + /// + /// Custom configuration file formats can be supported by constructing the + /// list of handlers and passing it to the appropriate \c FileOptionsProvider + /// constructor. E.g. initialization of a \c FileOptionsProvider with support + /// of a custom configuration file format for files named ".my-tidy-config" + /// could look similar to this: + /// \code + /// FileOptionsProvider::ConfigFileHandlers ConfigHandlers; + /// ConfigHandlers.emplace_back(".my-tidy-config", parseMyConfigFormat); + /// ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration); + /// return llvm::make_unique( + /// GlobalOptions, DefaultOptions, OverrideOptions, ConfigHandlers); + /// \endcode + /// + /// With the order of handlers shown above, the ".my-tidy-config" file would + /// take precedence over ".clang-tidy" if both reside in the same directory. + typedef std::vector ConfigFileHandlers; + + /// \brief Initializes the \c FileOptionsProvider instance. + /// + /// \param GlobalOptions are just stored and returned to the caller of + /// \c getGlobalOptions. + /// + /// \param DefaultOptions are used for all settings not specified in a + /// configuration file. + /// + /// If any of the \param OverrideOptions fields are set, they will override + /// whatever options are read from the configuration file. + FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions); + + /// \brief Initializes the \c FileOptionsProvider instance with a custom set + /// of configuration file handlers. + /// + /// \param GlobalOptions are just stored and returned to the caller of + /// \c getGlobalOptions. + /// + /// \param DefaultOptions are used for all settings not specified in a + /// configuration file. + /// + /// If any of the \param OverrideOptions fields are set, they will override + /// whatever options are read from the configuration file. + /// + /// \param ConfigHandlers specifies a custom set of configuration file + /// handlers. Each handler is a pair of configuration file name and a function + /// that can parse configuration from this file type. The configuration files + /// in each directory are searched for in the order of appearance in + /// \p ConfigHandlers. + FileOptionsProvider(const ClangTidyGlobalOptions &GlobalOptions, + const ClangTidyOptions &DefaultOptions, + const ClangTidyOptions &OverrideOptions, + const ConfigFileHandlers &ConfigHandlers); + + std::vector getRawOptions(llvm::StringRef FileName) override; + +protected: + /// \brief Try to read configuration files from \p Directory using registered + /// \c ConfigHandlers. + llvm::Optional tryReadConfigFile(llvm::StringRef Directory); + + llvm::StringMap CachedOptions; + ClangTidyOptions OverrideOptions; + ConfigFileHandlers ConfigHandlers; +}; + +/// \brief Parses LineFilter from JSON and stores it to the \p Options. +std::error_code parseLineFilter(llvm::StringRef LineFilter, + ClangTidyGlobalOptions &Options); + +/// \brief Parses configuration from JSON and returns \c ClangTidyOptions or an +/// error. +llvm::ErrorOr parseConfiguration(llvm::StringRef Config); + +/// \brief Serializes configuration to a YAML-encoded string. +std::string configurationAsText(const ClangTidyOptions &Options); + +} // end namespace tidy +} // end namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYOPTIONS_H diff --git a/clang-tidy/add_new_check.py b/clang-tidy/add_new_check.py new file mode 100755 index 000000000..7032ad9cf --- /dev/null +++ b/clang-tidy/add_new_check.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +# +#===- add_new_check.py - clang-tidy check generator ----------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +import os +import re +import sys + + +# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry +# and 'False' if the entry already existed. +def adapt_cmake(module_path, check_name_camel): + filename = os.path.join(module_path, 'CMakeLists.txt') + with open(filename, 'r') as f: + lines = f.readlines() + + cpp_file = check_name_camel + '.cpp' + + # Figure out whether this check already exists. + for line in lines: + if line.strip() == cpp_file: + return False + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + cpp_found = False + file_added = False + for line in lines: + cpp_line = line.strip().endswith('.cpp') + if (not file_added) and (cpp_line or cpp_found): + cpp_found = True + if (line.strip() > cpp_file) or (not cpp_line): + f.write(' ' + cpp_file + '\n') + file_added = True + f.write(line) + + return True + + +# Adds a header for the new check. +def write_header(module_path, module, check_name, check_name_camel): + check_name_dashes = module + '-' + check_name + filename = os.path.join(module_path, check_name_camel) + '.h' + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_' + + check_name.upper().replace('-', '_') + '_H') + f.write('//===--- ') + f.write(os.path.basename(filename)) + f.write(' - clang-tidy') + f.write('-' * max(0, 43 - len(os.path.basename(filename)))) + f.write('*- C++ -*-===//') + f.write(""" +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef %(header_guard)s +#define %(header_guard)s + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace %(module)s { + +/// FIXME: Write a short description. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html +class %(check_name)s : public ClangTidyCheck { +public: + %(check_name)s(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace %(module)s +} // namespace tidy +} // namespace clang + +#endif // %(header_guard)s +""" % {'header_guard': header_guard, + 'check_name': check_name_camel, + 'check_name_dashes': check_name_dashes, + 'module': module}) + + +# Adds the implementation of the new check. +def write_implementation(module_path, module, check_name_camel): + filename = os.path.join(module_path, check_name_camel) + '.cpp' + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write('//===--- ') + f.write(os.path.basename(filename)) + f.write(' - clang-tidy') + f.write('-' * max(0, 52 - len(os.path.basename(filename)))) + f.write('-===//') + f.write(""" +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "%(check_name)s.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace %(module)s { + +void %(check_name)s::registerMatchers(MatchFinder *Finder) { + // FIXME: Add matchers. + Finder->addMatcher(functionDecl().bind("x"), this); +} + +void %(check_name)s::check(const MatchFinder::MatchResult &Result) { + // FIXME: Add callback implementation. + const auto *MatchedDecl = Result.Nodes.getNodeAs("x"); + if (MatchedDecl->getName().startswith("awesome_")) + return; + diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome") + << MatchedDecl + << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); +} + +} // namespace %(module)s +} // namespace tidy +} // namespace clang +""" % {'check_name': check_name_camel, + 'module': module}) + + +# Modifies the module to include the new check. +def adapt_module(module_path, module, check_name, check_name_camel): + modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp', + os.listdir(module_path))[0] + filename = os.path.join(module_path, modulecpp) + with open(filename, 'r') as f: + lines = f.readlines() + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + header_added = False + header_found = False + check_added = False + check_decl = (' CheckFactories.registerCheck<' + check_name_camel + + '>(\n "' + module + '-' + check_name + '");\n') + + for line in lines: + if not header_added: + match = re.search('#include "(.*)"', line) + if match: + header_found = True + if match.group(1) > check_name_camel: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + elif header_found: + header_added = True + f.write('#include "' + check_name_camel + '.h"\n') + + if not check_added: + if line.strip() == '}': + check_added = True + f.write(check_decl) + else: + match = re.search('registerCheck<(.*)>', line) + if match and match.group(1) > check_name_camel: + check_added = True + f.write(check_decl) + f.write(line) + + +# Adds a test for the check. +def write_test(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy', + check_name_dashes + '.cpp')) + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t + +// FIXME: Add something that triggers the check here. +void f(); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s] + +// FIXME: Verify the applied fix. +// * Make the CHECK patterns specific enough and try to make verified lines +// unique to avoid incorrect matches. +// * Use {{}} for regular expressions. +// CHECK-FIXES: {{^}}void awesome_f();{{$}} + +// FIXME: Add something that doesn't trigger the check here. +void awesome_f2(); +""" % {'check_name_dashes': check_name_dashes}) + + +# Recreates the list of checks in the docs/clang-tidy/checks directory. +def update_checks_list(clang_tidy_path): + docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks') + filename = os.path.normpath(os.path.join(docs_dir, 'list.rst')) + with open(filename, 'r') as f: + lines = f.readlines() + doc_files = filter(lambda s: s.endswith('.rst') and s != 'list.rst', + os.listdir(docs_dir)) + doc_files.sort() + + def format_link(doc_file): + check_name = doc_file.replace('.rst', '') + with open(os.path.join(docs_dir, doc_file), 'r') as doc: + content = doc.read() + match = re.search('.*:orphan:.*', content) + if match: + return '' + + match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*', + content) + if match: + return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % { + 'check': check_name, + 'target': match.group(1) + } + return ' %s\n' % check_name + + checks = map(format_link, doc_files) + + print('Updating %s...' % filename) + with open(filename, 'wb') as f: + for line in lines: + f.write(line) + if line.startswith('.. toctree::'): + f.writelines(checks) + break + + +# Adds a documentation for the check. +def write_docs(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join( + module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst')) + print('Creating %s...' % filename) + with open(filename, 'wb') as f: + f.write(""".. title:: clang-tidy - %(check_name_dashes)s + +%(check_name_dashes)s +%(underline)s + +FIXME: Describe what patterns does the check detect and why. Give examples. +""" % {'check_name_dashes': check_name_dashes, + 'underline': '=' * len(check_name_dashes)}) + + +def main(): + if len(sys.argv) == 2 and sys.argv[1] == '--update-docs': + update_checks_list(os.path.dirname(sys.argv[0])) + return + + if len(sys.argv) != 3: + print """\ +Usage: add_new_check.py , e.g. + add_new_check.py misc awesome-functions + +Alternatively, run 'add_new_check.py --update-docs' to just update the list of +documentation files.""" + + return + + module = sys.argv[1] + check_name = sys.argv[2] + + if check_name.startswith(module): + print 'Check name "%s" must not start with the module "%s". Exiting.' % ( + check_name, module) + return + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + check_name.split('-'))) + 'Check' + clang_tidy_path = os.path.dirname(sys.argv[0]) + module_path = os.path.join(clang_tidy_path, module) + + if not adapt_cmake(module_path, check_name_camel): + return + write_header(module_path, module, check_name, check_name_camel) + write_implementation(module_path, module, check_name_camel) + adapt_module(module_path, module, check_name, check_name_camel) + write_test(module_path, module, check_name) + write_docs(module_path, module, check_name) + update_checks_list(clang_tidy_path) + print('Done. Now it\'s your turn!') + + +if __name__ == '__main__': + main() diff --git a/clang-tidy/boost/BoostTidyModule.cpp b/clang-tidy/boost/BoostTidyModule.cpp new file mode 100644 index 000000000..28eb1d656 --- /dev/null +++ b/clang-tidy/boost/BoostTidyModule.cpp @@ -0,0 +1,38 @@ +//===------- BoostTidyModule.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "UseToStringCheck.h" +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace boost { + +class BoostModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("boost-use-to-string"); + } +}; + +// Register the BoostModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("boost-module", + "Add boost checks."); + +} // namespace boost + +// This anchor is used to force the linker to link in the generated object file +// and thus register the BoostModule. +volatile int BoostModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/boost/CMakeLists.txt b/clang-tidy/boost/CMakeLists.txt new file mode 100644 index 000000000..059f6e91e --- /dev/null +++ b/clang-tidy/boost/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyBoostModule + BoostTidyModule.cpp + UseToStringCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + ) diff --git a/clang-tidy/boost/UseToStringCheck.cpp b/clang-tidy/boost/UseToStringCheck.cpp new file mode 100644 index 000000000..8ca3656ea --- /dev/null +++ b/clang-tidy/boost/UseToStringCheck.cpp @@ -0,0 +1,73 @@ +//===--- UseToStringCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseToStringCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace boost { + +AST_MATCHER(Type, isStrictlyInteger) { + return Node.isIntegerType() && !Node.isAnyCharacterType() && + !Node.isBooleanType(); +} + +void UseToStringCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + callExpr( + hasDeclaration(functionDecl( + returns(hasDeclaration(classTemplateSpecializationDecl( + hasName("std::basic_string"), + hasTemplateArgument(0, + templateArgument().bind("char_type"))))), + hasName("boost::lexical_cast"), + hasParameter(0, hasType(qualType(has(substTemplateTypeParmType( + isStrictlyInteger()))))))), + argumentCountIs(1), unless(isInTemplateInstantiation())) + .bind("to_string"), + this); +} + +void UseToStringCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("to_string"); + auto CharType = + Result.Nodes.getNodeAs("char_type")->getAsType(); + + StringRef StringType; + if (CharType->isSpecificBuiltinType(BuiltinType::Char_S) || + CharType->isSpecificBuiltinType(BuiltinType::Char_U)) + StringType = "string"; + else if (CharType->isSpecificBuiltinType(BuiltinType::WChar_S) || + CharType->isSpecificBuiltinType(BuiltinType::WChar_U)) + StringType = "wstring"; + else + return; + + auto Loc = Call->getLocStart(); + auto Diag = + diag(Loc, "use std::to_%0 instead of boost::lexical_cast") + << StringType; + + if (Loc.isMacroID()) + return; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(Call->getLocStart(), + Call->getArg(0)->getLocStart()), + (llvm::Twine("std::to_") + StringType + "(").str()); +} + +} // namespace boost +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/boost/UseToStringCheck.h b/clang-tidy/boost/UseToStringCheck.h new file mode 100644 index 000000000..76e7823bf --- /dev/null +++ b/clang-tidy/boost/UseToStringCheck.h @@ -0,0 +1,37 @@ +//===--- UseToStringCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USE_TO_STRING_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USE_TO_STRING_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace boost { + +/// Finds calls to ``boost::lexical_cast`` and +/// ``boost::lexical_cast`` and replaces them with +/// ``std::to_string`` and ``std::to_wstring`` calls. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/boost-use-to-string.html +class UseToStringCheck : public ClangTidyCheck { +public: + UseToStringCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace boost +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USE_TO_STRING_H diff --git a/clang-tidy/cert/CERTTidyModule.cpp b/clang-tidy/cert/CERTTidyModule.cpp new file mode 100644 index 000000000..d28f013aa --- /dev/null +++ b/clang-tidy/cert/CERTTidyModule.cpp @@ -0,0 +1,84 @@ +//===--- CERTTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../google/UnnamedNamespaceInHeaderCheck.h" +#include "../misc/MoveConstructorInitCheck.h" +#include "../misc/NewDeleteOverloadsCheck.h" +#include "../misc/NonCopyableObjects.h" +#include "../misc/StaticAssertCheck.h" +#include "../misc/ThrowByValueCatchByReferenceCheck.h" +#include "CommandProcessorCheck.h" +#include "FloatLoopCounter.h" +#include "LimitedRandomnessCheck.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-dcl50-cpp"); + CheckFactories.registerCheck( + "cert-dcl54-cpp"); + CheckFactories.registerCheck( + "cert-dcl59-cpp"); + // OOP + CheckFactories.registerCheck( + "cert-oop11-cpp"); + // ERR + CheckFactories.registerCheck( + "cert-err09-cpp"); + CheckFactories.registerCheck("cert-err52-cpp"); + CheckFactories.registerCheck("cert-err58-cpp"); + CheckFactories.registerCheck("cert-err60-cpp"); + CheckFactories.registerCheck( + "cert-err61-cpp"); + // MSC + CheckFactories.registerCheck("cert-msc50-cpp"); + + // C checkers + // DCL + CheckFactories.registerCheck("cert-dcl03-c"); + // ENV + CheckFactories.registerCheck("cert-env33-c"); + // FLP + CheckFactories.registerCheck("cert-flp30-c"); + // FIO + CheckFactories.registerCheck("cert-fio38-c"); + // ERR + CheckFactories.registerCheck("cert-err34-c"); + // MSC + CheckFactories.registerCheck("cert-msc30-c"); + } +}; + +} // namespace cert + +// Register the MiscTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("cert-module", + "Adds lint checks corresponding to CERT secure coding guidelines."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the CERTModule. +volatile int CERTModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/CMakeLists.txt b/clang-tidy/cert/CMakeLists.txt new file mode 100644 index 000000000..7a6b44a02 --- /dev/null +++ b/clang-tidy/cert/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCERTModule + CERTTidyModule.cpp + CommandProcessorCheck.cpp + FloatLoopCounter.cpp + LimitedRandomnessCheck.cpp + SetLongJmpCheck.cpp + StaticObjectExceptionCheck.cpp + StrToNumCheck.cpp + ThrownExceptionTypeCheck.cpp + VariadicFunctionDefCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyGoogleModule + clangTidyMiscModule + clangTidyUtils + ) diff --git a/clang-tidy/cert/CommandProcessorCheck.cpp b/clang-tidy/cert/CommandProcessorCheck.cpp new file mode 100644 index 000000000..e2dbeca20 --- /dev/null +++ b/clang-tidy/cert/CommandProcessorCheck.cpp @@ -0,0 +1,45 @@ +//===--- Env33CCheck.cpp - clang-tidy--------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "CommandProcessorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void CommandProcessorCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr( + callee(functionDecl(anyOf(hasName("::system"), hasName("::popen"), + hasName("::_popen"))) + .bind("func")), + // Do not diagnose when the call expression passes a null pointer + // constant to system(); that only checks for the presence of a + // command processor, which is not a security risk by itself. + unless(callExpr(callee(functionDecl(hasName("::system"))), + argumentCountIs(1), + hasArgument(0, nullPointerConstant())))) + .bind("expr"), + this); +} + +void CommandProcessorCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Fn = Result.Nodes.getNodeAs("func"); + const auto *E = Result.Nodes.getNodeAs("expr"); + + diag(E->getExprLoc(), "calling %0 uses a command processor") << Fn; +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/CommandProcessorCheck.h b/clang-tidy/cert/CommandProcessorCheck.h new file mode 100644 index 000000000..a85a7edae --- /dev/null +++ b/clang-tidy/cert/CommandProcessorCheck.h @@ -0,0 +1,38 @@ +//===--- CommandInterpreterCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_COMMAND_PROCESSOR_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_COMMAND_PROCESSOR_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Execution of a command processor can lead to security vulnerabilities, +/// and is generally not required. Instead, prefer to launch executables +/// directly via mechanisms that give you more control over what executable is +/// actually launched. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-env33-c.html +class CommandProcessorCheck : public ClangTidyCheck { +public: + CommandProcessorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_COMMAND_PROCESSOR_CHECK_H diff --git a/clang-tidy/cert/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/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..c465ecc59 --- /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) + return; + + // Match any static or thread_local variable declaration that has an + // initializer that can throw. + Finder->addMatcher( + varDecl(anyOf(hasThreadStorageDuration(), hasStaticStorageDuration()), + unless(hasAncestor(functionDecl())), + anyOf(hasDescendant(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(unless(isNoThrow())).bind("func")))), + hasDescendant(cxxNewExpr(hasDeclaration( + functionDecl(unless(isNoThrow())).bind("func")))), + hasDescendant(callExpr(hasDeclaration( + functionDecl(unless(isNoThrow())).bind("func")))))) + .bind("var"), + this); +} + +void StaticObjectExceptionCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("var"); + const auto *Func = Result.Nodes.getNodeAs("func"); + + diag(VD->getLocation(), + "initialization of %0 with %select{static|thread_local}1 storage " + "duration may throw an exception that cannot be caught") + << VD << (VD->getStorageDuration() == SD_Static ? 0 : 1); + + SourceLocation FuncLocation = Func->getLocation(); + if (FuncLocation.isValid()) { + diag(FuncLocation, + "possibly throwing %select{constructor|function}0 declared here", + DiagnosticIDs::Note) + << (isa(Func) ? 0 : 1); + } +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/StaticObjectExceptionCheck.h b/clang-tidy/cert/StaticObjectExceptionCheck.h new file mode 100644 index 000000000..463f43365 --- /dev/null +++ b/clang-tidy/cert/StaticObjectExceptionCheck.h @@ -0,0 +1,36 @@ +//===--- StaticObjectExceptionCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks whether the constructor for a static or thread_local object will +/// throw. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err58-cpp.html +class StaticObjectExceptionCheck : public ClangTidyCheck { +public: + StaticObjectExceptionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_ERR58_CPP_H diff --git a/clang-tidy/cert/StrToNumCheck.cpp b/clang-tidy/cert/StrToNumCheck.cpp new file mode 100644 index 000000000..bd84cf2b4 --- /dev/null +++ b/clang-tidy/cert/StrToNumCheck.cpp @@ -0,0 +1,235 @@ +//===--- Err34CCheck.cpp - clang-tidy--------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StrToNumCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/Analyses/FormatString.h" +#include "llvm/ADT/StringSwitch.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void StrToNumCheck::registerMatchers(MatchFinder *Finder) { + // Match any function call to the C standard library string conversion + // functions that do no error checking. + Finder->addMatcher( + callExpr( + callee(functionDecl(anyOf( + functionDecl(hasAnyName("::atoi", "::atof", "::atol", "::atoll")) + .bind("converter"), + functionDecl(hasAnyName("::scanf", "::sscanf", "::fscanf", + "::vfscanf", "::vscanf", "::vsscanf")) + .bind("formatted"))))) + .bind("expr"), + this); +} + +namespace { +enum class ConversionKind { + None, + ToInt, + ToUInt, + ToLongInt, + ToLongUInt, + ToIntMax, + ToUIntMax, + ToFloat, + ToDouble, + ToLongDouble +}; + +ConversionKind ClassifyConversionFunc(const FunctionDecl *FD) { + return llvm::StringSwitch(FD->getName()) + .Cases("atoi", "atol", ConversionKind::ToInt) + .Case("atoll", ConversionKind::ToLongInt) + .Case("atof", ConversionKind::ToDouble) + .Default(ConversionKind::None); +} + +ConversionKind ClassifyFormatString(StringRef Fmt, const LangOptions &LO, + const TargetInfo &TI) { + // Scan the format string for the first problematic format specifier, then + // report that as the conversion type. This will miss additional conversion + // specifiers, but that is acceptable behavior. + + class Handler : public analyze_format_string::FormatStringHandler { + ConversionKind CK; + + bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS, + const char *startSpecifier, + unsigned specifierLen) override { + // If we just consume the argument without assignment, we don't care + // about it having conversion errors. + if (!FS.consumesDataArgument()) + return true; + + // Get the conversion specifier and use it to determine the conversion + // kind. + analyze_scanf::ScanfConversionSpecifier SCS = FS.getConversionSpecifier(); + if (SCS.isIntArg()) { + switch (FS.getLengthModifier().getKind()) { + case analyze_scanf::LengthModifier::AsLongLong: + CK = ConversionKind::ToLongInt; + break; + case analyze_scanf::LengthModifier::AsIntMax: + CK = ConversionKind::ToIntMax; + break; + default: + CK = ConversionKind::ToInt; + break; + } + } else if (SCS.isUIntArg()) { + switch (FS.getLengthModifier().getKind()) { + case analyze_scanf::LengthModifier::AsLongLong: + CK = ConversionKind::ToLongUInt; + break; + case analyze_scanf::LengthModifier::AsIntMax: + CK = ConversionKind::ToUIntMax; + break; + default: + CK = ConversionKind::ToUInt; + break; + } + } else if (SCS.isDoubleArg()) { + switch (FS.getLengthModifier().getKind()) { + case analyze_scanf::LengthModifier::AsLongDouble: + CK = ConversionKind::ToLongDouble; + break; + case analyze_scanf::LengthModifier::AsLong: + CK = ConversionKind::ToDouble; + break; + default: + CK = ConversionKind::ToFloat; + break; + } + } + + // Continue if we have yet to find a conversion kind that we care about. + return CK == ConversionKind::None; + } + + public: + Handler() : CK(ConversionKind::None) {} + + ConversionKind get() const { return CK; } + }; + + Handler H; + analyze_format_string::ParseScanfString(H, Fmt.begin(), Fmt.end(), LO, TI); + + return H.get(); +} + +StringRef ClassifyConversionType(ConversionKind K) { + switch (K) { + case ConversionKind::None: + assert(false && "Unexpected conversion kind"); + case ConversionKind::ToInt: + case ConversionKind::ToLongInt: + case ConversionKind::ToIntMax: + return "an integer value"; + case ConversionKind::ToUInt: + case ConversionKind::ToLongUInt: + case ConversionKind::ToUIntMax: + return "an unsigned integer value"; + case ConversionKind::ToFloat: + case ConversionKind::ToDouble: + case ConversionKind::ToLongDouble: + return "a floating-point value"; + } + llvm_unreachable("Unknown conversion kind"); +} + +StringRef ClassifyReplacement(ConversionKind K) { + switch (K) { + case ConversionKind::None: + assert(false && "Unexpected conversion kind"); + case ConversionKind::ToInt: + return "strtol"; + case ConversionKind::ToUInt: + return "strtoul"; + case ConversionKind::ToIntMax: + return "strtoimax"; + case ConversionKind::ToLongInt: + return "strtoll"; + case ConversionKind::ToLongUInt: + return "strtoull"; + case ConversionKind::ToUIntMax: + return "strtoumax"; + case ConversionKind::ToFloat: + return "strtof"; + case ConversionKind::ToDouble: + return "strtod"; + case ConversionKind::ToLongDouble: + return "strtold"; + } + llvm_unreachable("Unknown conversion kind"); +} +} // unnamed namespace + +void StrToNumCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("expr"); + const FunctionDecl *FuncDecl = nullptr; + ConversionKind Conversion; + + if (const auto *ConverterFunc = + Result.Nodes.getNodeAs("converter")) { + // Converter functions are always incorrect to use. + FuncDecl = ConverterFunc; + Conversion = ClassifyConversionFunc(ConverterFunc); + } else if (const auto *FFD = + Result.Nodes.getNodeAs("formatted")) { + StringRef FmtStr; + // The format string comes from the call expression and depends on which + // flavor of scanf is called. + // Index 0: scanf, vscanf, Index 1: fscanf, sscanf, vfscanf, vsscanf. + unsigned Idx = + (FFD->getName() == "scanf" || FFD->getName() == "vscanf") ? 0 : 1; + + // Given the index, see if the call expression argument at that index is + // a string literal. + if (Call->getNumArgs() < Idx) + return; + + if (const Expr *Arg = Call->getArg(Idx)->IgnoreParenImpCasts()) { + if (const auto *SL = dyn_cast(Arg)) { + FmtStr = SL->getString(); + } + } + + // If we could not get the format string, bail out. + if (FmtStr.empty()) + return; + + // Formatted input functions need further checking of the format string to + // determine whether a problematic conversion may be happening. + Conversion = ClassifyFormatString(FmtStr, getLangOpts(), + Result.Context->getTargetInfo()); + if (Conversion != ConversionKind::None) + FuncDecl = FFD; + } + + if (!FuncDecl) + return; + + diag(Call->getExprLoc(), + "%0 used to convert a string to %1, but function will not report " + "conversion errors; consider using '%2' instead") + << FuncDecl << ClassifyConversionType(Conversion) + << ClassifyReplacement(Conversion); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/StrToNumCheck.h b/clang-tidy/cert/StrToNumCheck.h new file mode 100644 index 000000000..55f13bead --- /dev/null +++ b/clang-tidy/cert/StrToNumCheck.h @@ -0,0 +1,36 @@ +//===--- StrToNumCheck.h - clang-tidy----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_STRTONUMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_STRTONUMCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against use of string conversion functions that do not have +/// reasonable error handling for conversion errors. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err34-c.html +class StrToNumCheck : public ClangTidyCheck { +public: + StrToNumCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_STRTONUMCHECK_H diff --git a/clang-tidy/cert/ThrownExceptionTypeCheck.cpp b/clang-tidy/cert/ThrownExceptionTypeCheck.cpp new file mode 100644 index 000000000..37fb355ff --- /dev/null +++ b/clang-tidy/cert/ThrownExceptionTypeCheck.cpp @@ -0,0 +1,41 @@ +//===--- ThrownExceptionTypeCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ThrownExceptionTypeCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void ThrownExceptionTypeCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxThrowExpr(has(ignoringParenImpCasts( + cxxConstructExpr(hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), unless(isNoThrow())))) + .bind("expr")))), + this); +} + +void ThrownExceptionTypeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + diag(E->getExprLoc(), + "thrown exception type is not nothrow copy constructible"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/ThrownExceptionTypeCheck.h b/clang-tidy/cert/ThrownExceptionTypeCheck.h new file mode 100644 index 000000000..2f9d887f2 --- /dev/null +++ b/clang-tidy/cert/ThrownExceptionTypeCheck.h @@ -0,0 +1,35 @@ +//===--- ThrownExceptionTypeCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checks whether a thrown object is nothrow copy constructible. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-err60-cpp.html +class ThrownExceptionTypeCheck : public ClangTidyCheck { +public: + ThrownExceptionTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_THROWNEXCEPTIONTYPECHECK_H diff --git a/clang-tidy/cert/VariadicFunctionDefCheck.cpp b/clang-tidy/cert/VariadicFunctionDefCheck.cpp new file mode 100644 index 000000000..ea6112a6d --- /dev/null +++ b/clang-tidy/cert/VariadicFunctionDefCheck.cpp @@ -0,0 +1,42 @@ +//===--- VariadicfunctiondefCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VariadicFunctionDefCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +void VariadicFunctionDefCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // We only care about function *definitions* that are variadic, and do not + // have extern "C" language linkage. + Finder->addMatcher( + functionDecl(isDefinition(), isVariadic(), unless(isExternC())) + .bind("func"), + this); +} + +void VariadicFunctionDefCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FD = Result.Nodes.getNodeAs("func"); + + diag(FD->getLocation(), + "do not define a C-style variadic function; consider using a function " + "parameter pack or currying instead"); +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cert/VariadicFunctionDefCheck.h b/clang-tidy/cert/VariadicFunctionDefCheck.h new file mode 100644 index 000000000..e215e8df8 --- /dev/null +++ b/clang-tidy/cert/VariadicFunctionDefCheck.h @@ -0,0 +1,35 @@ +//===--- VariadicFunctionDefCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Guards against any C-style variadic function definitions (not declarations). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-dcl50-cpp.html +class VariadicFunctionDefCheck : public ClangTidyCheck { +public: + VariadicFunctionDefCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_VARIADICFUNCTIONDEF_H diff --git a/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tidy/cppcoreguidelines/CMakeLists.txt new file mode 100644 index 000000000..43ac55d1d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -0,0 +1,29 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyCppCoreGuidelinesModule + CppCoreGuidelinesTidyModule.cpp + InterfacesGlobalInitCheck.cpp + NoMallocCheck.cpp + ProBoundsArrayToPointerDecayCheck.cpp + ProBoundsConstantArrayIndexCheck.cpp + ProBoundsPointerArithmeticCheck.cpp + ProTypeConstCastCheck.cpp + ProTypeCstyleCastCheck.cpp + ProTypeMemberInitCheck.cpp + ProTypeReinterpretCastCheck.cpp + ProTypeStaticCastDowncastCheck.cpp + ProTypeUnionAccessCheck.cpp + ProTypeVarargCheck.cpp + SpecialMemberFunctionsCheck.cpp + SlicingCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyMiscModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp new file mode 100644 index 000000000..6b9de8d39 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -0,0 +1,79 @@ +//===--- CppCoreGuidelinesModule.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../misc/UnconventionalAssignOperatorCheck.h" +#include "InterfacesGlobalInitCheck.h" +#include "NoMallocCheck.h" +#include "ProBoundsArrayToPointerDecayCheck.h" +#include "ProBoundsConstantArrayIndexCheck.h" +#include "ProBoundsPointerArithmeticCheck.h" +#include "ProTypeConstCastCheck.h" +#include "ProTypeCstyleCastCheck.h" +#include "ProTypeMemberInitCheck.h" +#include "ProTypeReinterpretCastCheck.h" +#include "ProTypeStaticCastDowncastCheck.h" +#include "ProTypeUnionAccessCheck.h" +#include "ProTypeVarargCheck.h" +#include "SlicingCheck.h" +#include "SpecialMemberFunctionsCheck.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// A module containing checks of the C++ Core Guidelines +class CppCoreGuidelinesModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "cppcoreguidelines-interfaces-global-init"); + CheckFactories.registerCheck("cppcoreguidelines-no-malloc"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-array-to-pointer-decay"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-constant-array-index"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-pointer-arithmetic"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-const-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-cstyle-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-member-init"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-reinterpret-cast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-static-cast-downcast"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-union-access"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-type-vararg"); + CheckFactories.registerCheck( + "cppcoreguidelines-special-member-functions"); + CheckFactories.registerCheck("cppcoreguidelines-slicing"); + CheckFactories.registerCheck( + "cppcoreguidelines-c-copy-assignment-signature"); + } +}; + +// Register the LLVMTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("cppcoreguidelines-module", "Adds checks for the C++ Core Guidelines."); + +} // namespace cppcoreguidelines + +// This anchor is used to force the linker to link in the generated object file +// and thus register the CppCoreGuidelinesModule. +volatile int CppCoreGuidelinesModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp new file mode 100644 index 000000000..f601b2443 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp @@ -0,0 +1,59 @@ +//===--- InterfacesGlobalInitCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InterfacesGlobalInitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void InterfacesGlobalInitCheck::registerMatchers(MatchFinder *Finder) { + const auto IsGlobal = + allOf(hasGlobalStorage(), + hasDeclContext(anyOf(translationUnitDecl(), // Global scope. + namespaceDecl(), // Namespace scope. + recordDecl())), // Class scope. + unless(isConstexpr())); + + const auto ReferencesUndefinedGlobalVar = declRefExpr(hasDeclaration( + varDecl(IsGlobal, unless(isDefinition())).bind("referencee"))); + + Finder->addMatcher( + varDecl(IsGlobal, isDefinition(), + hasInitializer(expr(hasDescendant(ReferencesUndefinedGlobalVar)))) + .bind("var"), + this); +} + +void InterfacesGlobalInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *const Var = Result.Nodes.getNodeAs("var"); + // For now assume that people who write macros know what they're doing. + if (Var->getLocation().isMacroID()) + return; + const auto *const Referencee = Result.Nodes.getNodeAs("referencee"); + // If the variable has been defined, we're good. + const auto *const ReferenceeDef = Referencee->getDefinition(); + if (ReferenceeDef != nullptr && + Result.SourceManager->isBeforeInTranslationUnit( + ReferenceeDef->getLocation(), Var->getLocation())) { + return; + } + diag(Var->getLocation(), + "initializing non-local variable with non-const expression depending on " + "uninitialized non-local variable %0") + << Referencee; +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h new file mode 100644 index 000000000..13712d11e --- /dev/null +++ b/clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h @@ -0,0 +1,35 @@ +//===--- InterfacesGlobalInitCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_INTERFACES_GLOBAL_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_INTERFACES_GLOBAL_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Flags possible initialization order issues of static variables. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.html +class InterfacesGlobalInitCheck : public ClangTidyCheck { +public: + InterfacesGlobalInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_INTERFACES_GLOBAL_INIT_H diff --git a/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp new file mode 100644 index 000000000..d747fca6c --- /dev/null +++ b/clang-tidy/cppcoreguidelines/NoMallocCheck.cpp @@ -0,0 +1,62 @@ +//===--- 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 "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +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(hasAnyName("::malloc", "::calloc")))) + .bind("aquisition"), + this); + + // Registering realloc calls, suggest std::vector or std::string. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::realloc")))).bind("realloc"), + this); + + // Registering free calls, will suggest RAII instead. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::free")))).bind("free"), this); +} + +void NoMallocCheck::check(const MatchFinder::MatchResult &Result) { + const CallExpr *Call = nullptr; + StringRef Recommendation; + + if ((Call = Result.Nodes.getNodeAs("aquisition"))) + 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..cf2a8a27a --- /dev/null +++ b/clang-tidy/cppcoreguidelines/NoMallocCheck.h @@ -0,0 +1,44 @@ +//===--- 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: + NoMallocCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + /// 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; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_NO_MALLOC_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp new file mode 100644 index 000000000..bfcef89e6 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp @@ -0,0 +1,80 @@ +//===--- ProBoundsArrayToPointerDecayCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsArrayToPointerDecayCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +AST_MATCHER_P(CXXForRangeStmt, hasRangeBeginEndStmt, + ast_matchers::internal::Matcher, InnerMatcher) { + for (const DeclStmt *Stmt : {Node.getBeginStmt(), Node.getEndStmt()}) + if (Stmt != nullptr && InnerMatcher.matches(*Stmt, Finder, Builder)) + return true; + return false; +} + +AST_MATCHER(Stmt, isInsideOfRangeBeginEndStmt) { + return stmt(hasAncestor(cxxForRangeStmt( + hasRangeBeginEndStmt(hasDescendant(equalsNode(&Node)))))) + .matches(Node, Finder, Builder); +} + +AST_MATCHER_P(Expr, hasParentIgnoringImpCasts, + ast_matchers::internal::Matcher, InnerMatcher) { + const Expr *E = &Node; + do { + ASTContext::DynTypedNodeList Parents = + Finder->getASTContext().getParents(*E); + if (Parents.size() != 1) + return false; + E = Parents[0].get(); + if (!E) + return false; + } while (isa(E)); + + return InnerMatcher.matches(*E, Finder, Builder); +} + +void ProBoundsArrayToPointerDecayCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // The only allowed array to pointer decay + // 1) just before array subscription + // 2) inside a range-for over an array + // 3) if it converts a string literal to a pointer + Finder->addMatcher( + implicitCastExpr(unless(hasParent(arraySubscriptExpr())), + unless(hasParentIgnoringImpCasts(explicitCastExpr())), + unless(isInsideOfRangeBeginEndStmt()), + unless(hasSourceExpression(stringLiteral()))) + .bind("cast"), + this); +} + +void ProBoundsArrayToPointerDecayCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + if (MatchedCast->getCastKind() != CK_ArrayToPointerDecay) + return; + + diag(MatchedCast->getExprLoc(), "do not implicitly decay an array into a " + "pointer; consider using gsl::array_view or " + "an explicit cast instead"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h new file mode 100644 index 000000000..0afffb64f --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.h @@ -0,0 +1,35 @@ +//===--- ProBoundsArrayToPointerDecayCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all array to pointer decays +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-array-to-pointer-decay.html +class ProBoundsArrayToPointerDecayCheck : public ClangTidyCheck { +public: + ProBoundsArrayToPointerDecayCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_ARRAY_TO_POINTER_DECAY_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp new file mode 100644 index 000000000..3844f68ed --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.cpp @@ -0,0 +1,141 @@ +//===--- ProBoundsConstantArrayIndexCheck.cpp - clang-tidy-----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsConstantArrayIndexCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +ProBoundsConstantArrayIndexCheck::ProBoundsConstantArrayIndexCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), GslHeader(Options.get("GslHeader", "")), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ProBoundsConstantArrayIndexCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "GslHeader", GslHeader); + Options.store(Opts, "IncludeStyle", IncludeStyle); +} + +void ProBoundsConstantArrayIndexCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + if (!getLangOpts().CPlusPlus) + return; + + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void ProBoundsConstantArrayIndexCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Note: if a struct contains an array member, the compiler-generated + // constructor has an arraySubscriptExpr. + Finder->addMatcher( + arraySubscriptExpr( + hasBase(ignoringImpCasts(hasType(constantArrayType().bind("type")))), + hasIndex(expr().bind("index")), unless(hasAncestor(isImplicit()))) + .bind("expr"), + this); + + Finder->addMatcher( + cxxOperatorCallExpr( + hasOverloadedOperatorName("[]"), + hasArgument( + 0, hasType(cxxRecordDecl(hasName("::std::array")).bind("type"))), + hasArgument(1, expr().bind("index"))) + .bind("expr"), + this); +} + +void ProBoundsConstantArrayIndexCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Matched = Result.Nodes.getNodeAs("expr"); + const auto *IndexExpr = Result.Nodes.getNodeAs("index"); + + if (IndexExpr->isValueDependent()) + return; // We check in the specialization. + + llvm::APSInt Index; + if (!IndexExpr->isIntegerConstantExpr(Index, *Result.Context, nullptr, + /*isEvaluated=*/true)) { + SourceRange BaseRange; + if (const auto *ArraySubscriptE = dyn_cast(Matched)) + BaseRange = ArraySubscriptE->getBase()->getSourceRange(); + else + BaseRange = + dyn_cast(Matched)->getArg(0)->getSourceRange(); + SourceRange IndexRange = IndexExpr->getSourceRange(); + + auto Diag = diag(Matched->getExprLoc(), + "do not use array subscript when the index is " + "not an integer constant expression; use gsl::at() " + "instead"); + if (!GslHeader.empty()) { + Diag << FixItHint::CreateInsertion(BaseRange.getBegin(), "gsl::at(") + << FixItHint::CreateReplacement( + SourceRange(BaseRange.getEnd().getLocWithOffset(1), + IndexRange.getBegin().getLocWithOffset(-1)), + ", ") + << FixItHint::CreateReplacement(Matched->getLocEnd(), ")"); + + Optional Insertion = Inserter->CreateIncludeInsertion( + Result.SourceManager->getMainFileID(), GslHeader, + /*IsAngled=*/false); + if (Insertion) + Diag << Insertion.getValue(); + } + return; + } + + const auto *StdArrayDecl = + Result.Nodes.getNodeAs("type"); + + // For static arrays, this is handled in clang-diagnostic-array-bounds. + if (!StdArrayDecl) + return; + + if (Index.isSigned() && Index.isNegative()) { + diag(Matched->getExprLoc(), "std::array<> index %0 is negative") + << Index.toString(10); + return; + } + + const TemplateArgumentList &TemplateArgs = StdArrayDecl->getTemplateArgs(); + if (TemplateArgs.size() < 2) + return; + // First template arg of std::array is the type, second arg is the size. + const auto &SizeArg = TemplateArgs[1]; + if (SizeArg.getKind() != TemplateArgument::Integral) + return; + llvm::APInt ArraySize = SizeArg.getAsIntegral(); + + // Get uint64_t values, because different bitwidths would lead to an assertion + // in APInt::uge. + if (Index.getZExtValue() >= ArraySize.getZExtValue()) { + diag(Matched->getExprLoc(), + "std::array<> index %0 is past the end of the array " + "(which contains %1 elements)") + << Index.toString(10) << ArraySize.toString(10, false); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h new file mode 100644 index 000000000..28b24a6b8 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsConstantArrayIndexCheck.h @@ -0,0 +1,42 @@ +//===--- ProBoundsConstantArrayIndexCheck.h - clang-tidy---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This checks that all array subscriptions on static arrays and std::arrays +/// have a constant index and are within bounds +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-constant-array-index.html +class ProBoundsConstantArrayIndexCheck : public ClangTidyCheck { + const std::string GslHeader; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + std::unique_ptr Inserter; + +public: + ProBoundsConstantArrayIndexCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_CONSTANT_ARRAY_INDEX_H diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp new file mode 100644 index 000000000..d664b6401 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.cpp @@ -0,0 +1,59 @@ +//===--- ProBoundsPointerArithmeticCheck.cpp - clang-tidy------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProBoundsPointerArithmeticCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProBoundsPointerArithmeticCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Flag all operators +, -, +=, -=, ++, -- that result in a pointer + Finder->addMatcher( + binaryOperator( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("+="), hasOperatorName("-=")), + hasType(pointerType()), + unless(hasLHS(ignoringImpCasts(declRefExpr(to(isImplicit())))))) + .bind("expr"), + this); + + Finder->addMatcher( + unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), + hasType(pointerType())) + .bind("expr"), + this); + + // Array subscript on a pointer (not an array) is also pointer arithmetic + Finder->addMatcher( + arraySubscriptExpr( + hasBase(ignoringImpCasts( + anyOf(hasType(pointerType()), + hasType(decayedType(hasDecayedType(pointerType()))))))) + .bind("expr"), + this); +} + +void ProBoundsPointerArithmeticCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedExpr = Result.Nodes.getNodeAs("expr"); + + diag(MatchedExpr->getExprLoc(), "do not use pointer arithmetic"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h new file mode 100644 index 000000000..5ecf93cc1 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProBoundsPointerArithmeticCheck.h @@ -0,0 +1,37 @@ +//===--- ProBoundsPointerArithmeticCheck.h - clang-tidy----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Flags all kinds of pointer arithmetic that have result of pointer type, i.e. +/// +, -, +=, -=, ++, --. In addition, the [] operator on pointers (not on +/// arrays) is flagged. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-bounds-pointer-arithmetic.html +class ProBoundsPointerArithmeticCheck : public ClangTidyCheck { +public: + ProBoundsPointerArithmeticCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_POINTER_ARITHMETIC_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp new file mode 100644 index 000000000..4b6fb4207 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.cpp @@ -0,0 +1,34 @@ +//===--- ProTypeConstCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeConstCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +void ProTypeConstCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(cxxConstCastExpr().bind("cast"), this); +} + +void ProTypeConstCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + diag(MatchedCast->getOperatorLoc(), "do not use const_cast"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h new file mode 100644 index 000000000..92a3a1b5d --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeConstCastCheck.h @@ -0,0 +1,35 @@ +//===--- ProTypeConstCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all instances of const_cast +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-const-cast.html +class ProTypeConstCastCheck : public ClangTidyCheck { +public: + ProTypeConstCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CONST_CAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp new file mode 100644 index 000000000..52156ad22 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.cpp @@ -0,0 +1,108 @@ +//===--- ProTypeCstyleCastCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeCstyleCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +static bool needsConstCast(QualType SourceType, QualType DestType) { + SourceType = SourceType.getNonReferenceType(); + DestType = DestType.getNonReferenceType(); + while (SourceType->isPointerType() && DestType->isPointerType()) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + if (SourceType.isConstQualified() && !DestType.isConstQualified()) + return true; + } + return false; +} + +void ProTypeCstyleCastCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cStyleCastExpr(unless(isInTemplateInstantiation())).bind("cast"), this); +} + +void ProTypeCstyleCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedCast = Result.Nodes.getNodeAs("cast"); + + if (MatchedCast->getCastKind() == CK_BitCast || + MatchedCast->getCastKind() == CK_LValueBitCast || + MatchedCast->getCastKind() == CK_IntegralToPointer || + MatchedCast->getCastKind() == CK_PointerToIntegral || + MatchedCast->getCastKind() == CK_ReinterpretMemberPointer) { + diag(MatchedCast->getLocStart(), + "do not use C-style cast to convert between unrelated types"); + return; + } + + QualType SourceType = MatchedCast->getSubExpr()->getType(); + + if (MatchedCast->getCastKind() == CK_BaseToDerived) { + const auto *SourceDecl = SourceType->getPointeeCXXRecordDecl(); + if (!SourceDecl) // The cast is from object to reference. + SourceDecl = SourceType->getAsCXXRecordDecl(); + if (!SourceDecl) + return; + + if (SourceDecl->isPolymorphic()) { + // Leave type spelling exactly as it was (unlike + // getTypeAsWritten().getAsString() which would spell enum types 'enum + // X'). + StringRef DestTypeString = Lexer::getSourceText( + CharSourceRange::getTokenRange( + MatchedCast->getLParenLoc().getLocWithOffset(1), + MatchedCast->getRParenLoc().getLocWithOffset(-1)), + *Result.SourceManager, getLangOpts()); + + auto diag_builder = diag( + MatchedCast->getLocStart(), + "do not use C-style cast to downcast from a base to a derived class; " + "use dynamic_cast instead"); + + const Expr *SubExpr = + MatchedCast->getSubExprAsWritten()->IgnoreImpCasts(); + std::string CastText = ("dynamic_cast<" + DestTypeString + ">").str(); + if (!isa(SubExpr)) { + CastText.push_back('('); + diag_builder << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, + *Result.SourceManager, getLangOpts()), + ")"); + } + auto ParenRange = CharSourceRange::getTokenRange( + MatchedCast->getLParenLoc(), MatchedCast->getRParenLoc()); + diag_builder << FixItHint::CreateReplacement(ParenRange, CastText); + } else { + diag( + MatchedCast->getLocStart(), + "do not use C-style cast to downcast from a base to a derived class"); + } + return; + } + + if (MatchedCast->getCastKind() == CK_NoOp && + needsConstCast(SourceType, MatchedCast->getType())) { + diag(MatchedCast->getLocStart(), + "do not use C-style cast to cast away constness"); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h new file mode 100644 index 000000000..c08b883c0 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeCstyleCastCheck.h @@ -0,0 +1,36 @@ +//===--- ProTypeCstyleCastCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check flags all use of C-style casts that perform a static_cast +/// downcast, const_cast, or reinterpret_cast. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-pro-type-cstyle-cast.html +class ProTypeCstyleCastCheck : public ClangTidyCheck { +public: + ProTypeCstyleCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_TYPE_CSTYLE_CAST_H diff --git a/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp new file mode 100644 index 000000000..804b2b057 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp @@ -0,0 +1,482 @@ +//===--- ProTypeMemberInitCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProTypeMemberInitCheck.h" +#include "../utils/LexerUtils.h" +#include "../utils/Matchers.h" +#include "../utils/TypeTraits.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallPtrSet.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; +using llvm::SmallPtrSet; +using llvm::SmallPtrSetImpl; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { + +AST_MATCHER(CXXRecordDecl, hasDefaultConstructor) { + return Node.hasDefaultConstructor(); +} + +// Iterate over all the fields in a record type, both direct and indirect (e.g. +// if the record contains an anonmyous struct). If OneFieldPerUnion is true and +// the record type (or indirect field) is a union, forEachField will stop after +// the first field. +template +void forEachField(const RecordDecl &Record, const T &Fields, + bool OneFieldPerUnion, Func &&Fn) { + for (const FieldDecl *F : Fields) { + if (F->isAnonymousStructOrUnion()) { + if (const CXXRecordDecl *R = F->getType()->getAsCXXRecordDecl()) + forEachField(*R, R->fields(), OneFieldPerUnion, Fn); + } else { + Fn(F); + } + + if (OneFieldPerUnion && Record.isUnion()) + break; + } +} + +void removeFieldsInitializedInBody( + const Stmt &Stmt, ASTContext &Context, + SmallPtrSetImpl &FieldDecls) { + auto Matches = + match(findAll(binaryOperator( + hasOperatorName("="), + hasLHS(memberExpr(member(fieldDecl().bind("fieldDecl")))))), + Stmt, Context); + for (const auto &Match : Matches) + FieldDecls.erase(Match.getNodeAs("fieldDecl")); +} + +StringRef getName(const FieldDecl *Field) { return Field->getName(); } + +StringRef getName(const RecordDecl *Record) { + // Get the typedef name if this is a C-style anonymous struct and typedef. + if (const TypedefNameDecl *Typedef = Record->getTypedefNameForAnonDecl()) + return Typedef->getName(); + return Record->getName(); +} + +// Creates comma separated list of decls requiring initialization in order of +// declaration. +template +std::string +toCommaSeparatedString(const R &OrderedDecls, + const SmallPtrSetImpl &DeclsToInit) { + SmallVector Names; + for (const T *Decl : OrderedDecls) { + if (DeclsToInit.count(Decl)) + Names.emplace_back(getName(Decl)); + } + return llvm::join(Names.begin(), Names.end(), ", "); +} + +SourceLocation getLocationForEndOfToken(const ASTContext &Context, + SourceLocation Location) { + return Lexer::getLocForEndOfToken(Location, 0, Context.getSourceManager(), + Context.getLangOpts()); +} + +// There are 3 kinds of insertion placements: +enum class InitializerPlacement { + // 1. The fields are inserted after an existing CXXCtorInitializer stored in + // Where. This will be the case whenever there is a written initializer before + // the fields available. + After, + + // 2. The fields are inserted before the first existing initializer stored in + // Where. + Before, + + // 3. There are no written initializers and the fields will be inserted before + // the constructor's body creating a new initializer list including the ':'. + New +}; + +// An InitializerInsertion contains a list of fields and/or base classes to +// insert into the initializer list of a constructor. We use this to ensure +// proper absolute ordering according to the class declaration relative to the +// (perhaps improper) ordering in the existing initializer list, if any. +struct IntializerInsertion { + IntializerInsertion(InitializerPlacement Placement, + const CXXCtorInitializer *Where) + : Placement(Placement), Where(Where) {} + + SourceLocation getLocation(const ASTContext &Context, + const CXXConstructorDecl &Constructor) const { + assert((Where != nullptr || Placement == InitializerPlacement::New) && + "Location should be relative to an existing initializer or this " + "insertion represents a new initializer list."); + SourceLocation Location; + switch (Placement) { + case InitializerPlacement::New: + Location = utils::lexer::getPreviousNonCommentToken( + Context, Constructor.getBody()->getLocStart()) + .getLocation(); + break; + case InitializerPlacement::Before: + Location = utils::lexer::getPreviousNonCommentToken( + Context, Where->getSourceRange().getBegin()) + .getLocation(); + break; + case InitializerPlacement::After: + Location = Where->getRParenLoc(); + break; + } + return getLocationForEndOfToken(Context, Location); + } + + std::string codeToInsert() const { + assert(!Initializers.empty() && "No initializers to insert"); + std::string Code; + llvm::raw_string_ostream Stream(Code); + std::string joined = + llvm::join(Initializers.begin(), Initializers.end(), "(), "); + switch (Placement) { + case InitializerPlacement::New: + Stream << " : " << joined << "()"; + break; + case InitializerPlacement::Before: + Stream << " " << joined << "(),"; + break; + case InitializerPlacement::After: + Stream << ", " << joined << "()"; + break; + } + return Stream.str(); + } + + InitializerPlacement Placement; + const CXXCtorInitializer *Where; + SmallVector Initializers; +}; + +// Convenience utility to get a RecordDecl from a QualType. +const RecordDecl *getCanonicalRecordDecl(const QualType &Type) { + if (const auto *RT = Type.getCanonicalType()->getAs()) + return RT->getDecl(); + return nullptr; +} + +template +SmallVector +computeInsertions(const CXXConstructorDecl::init_const_range &Inits, + const R &OrderedDecls, + const SmallPtrSetImpl &DeclsToInit) { + SmallVector Insertions; + Insertions.emplace_back(InitializerPlacement::New, nullptr); + + typename R::const_iterator Decl = std::begin(OrderedDecls); + for (const CXXCtorInitializer *Init : Inits) { + if (Init->isWritten()) { + if (Insertions.size() == 1) + Insertions.emplace_back(InitializerPlacement::Before, Init); + + // Gets either the field or base class being initialized by the provided + // initializer. + const auto *InitDecl = + Init->isAnyMemberInitializer() + ? static_cast(Init->getAnyMember()) + : Init->getBaseClass()->getAsCXXRecordDecl(); + + // Add all fields between current field up until the next intializer. + for (; Decl != std::end(OrderedDecls) && *Decl != InitDecl; ++Decl) { + if (const auto *D = dyn_cast(*Decl)) { + if (DeclsToInit.count(D) > 0) + Insertions.back().Initializers.emplace_back(getName(D)); + } + } + + Insertions.emplace_back(InitializerPlacement::After, Init); + } + } + + // Add remaining decls that require initialization. + for (; Decl != std::end(OrderedDecls); ++Decl) { + if (const auto *D = dyn_cast(*Decl)) { + if (DeclsToInit.count(D) > 0) + Insertions.back().Initializers.emplace_back(getName(D)); + } + } + return Insertions; +} + +// Gets the list of bases and members that could possibly be initialized, in +// order as they appear in the class declaration. +void getInitializationsInOrder(const CXXRecordDecl &ClassDecl, + SmallVectorImpl &Decls) { + Decls.clear(); + for (const auto &Base : ClassDecl.bases()) { + // Decl may be null if the base class is a template parameter. + if (const NamedDecl *Decl = getCanonicalRecordDecl(Base.getType())) { + Decls.emplace_back(Decl); + } + } + forEachField(ClassDecl, ClassDecl.fields(), false, + [&](const FieldDecl *F) { Decls.push_back(F); }); +} + +template +void fixInitializerList(const ASTContext &Context, DiagnosticBuilder &Diag, + const CXXConstructorDecl *Ctor, + const SmallPtrSetImpl &DeclsToInit) { + // Do not propose fixes in macros since we cannot place them correctly. + if (Ctor->getLocStart().isMacroID()) + return; + + SmallVector OrderedDecls; + getInitializationsInOrder(*Ctor->getParent(), OrderedDecls); + + for (const auto &Insertion : + computeInsertions(Ctor->inits(), OrderedDecls, DeclsToInit)) { + if (!Insertion.Initializers.empty()) + Diag << FixItHint::CreateInsertion(Insertion.getLocation(Context, *Ctor), + Insertion.codeToInsert()); + } +} + +} // anonymous namespace + +ProTypeMemberInitCheck::ProTypeMemberInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreArrays(Options.get("IgnoreArrays", false)) {} + +void ProTypeMemberInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto IsUserProvidedNonDelegatingConstructor = + allOf(isUserProvided(), + unless(anyOf(isInstantiated(), isDelegatingConstructor()))); + auto IsNonTrivialDefaultConstructor = allOf( + isDefaultConstructor(), unless(isUserProvided()), + hasParent(cxxRecordDecl(unless(isTriviallyDefaultConstructible())))); + Finder->addMatcher( + cxxConstructorDecl(isDefinition(), + anyOf(IsUserProvidedNonDelegatingConstructor, + IsNonTrivialDefaultConstructor)) + .bind("ctor"), + this); + + // Match classes with a default constructor that is defaulted or is not in the + // AST. + Finder->addMatcher( + cxxRecordDecl( + isDefinition(), unless(isInstantiated()), hasDefaultConstructor(), + anyOf(has(cxxConstructorDecl(isDefaultConstructor(), isDefaulted(), + unless(isImplicit()))), + unless(has(cxxConstructorDecl()))), + unless(isTriviallyDefaultConstructible())) + .bind("record"), + this); + + auto HasDefaultConstructor = hasInitializer( + cxxConstructExpr(unless(requiresZeroInitialization()), + hasDeclaration(cxxConstructorDecl( + isDefaultConstructor(), unless(isUserProvided()))))); + Finder->addMatcher( + varDecl(isDefinition(), HasDefaultConstructor, + hasAutomaticStorageDuration(), + hasType(recordDecl(has(fieldDecl()), + isTriviallyDefaultConstructible()))) + .bind("var"), + this); +} + +void ProTypeMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Ctor = Result.Nodes.getNodeAs("ctor")) { + // Skip declarations delayed by late template parsing without a body. + if (!Ctor->getBody()) + return; + checkMissingMemberInitializer(*Result.Context, *Ctor->getParent(), Ctor); + checkMissingBaseClassInitializer(*Result.Context, *Ctor->getParent(), Ctor); + } else if (const auto *Record = + Result.Nodes.getNodeAs("record")) { + assert(Record->hasDefaultConstructor() && + "Matched record should have a default constructor"); + checkMissingMemberInitializer(*Result.Context, *Record, nullptr); + checkMissingBaseClassInitializer(*Result.Context, *Record, nullptr); + } else if (const auto *Var = Result.Nodes.getNodeAs("var")) { + checkUninitializedTrivialType(*Result.Context, Var); + } +} + +void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreArrays", IgnoreArrays); +} + +// FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp. +static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) { + if (T->isIncompleteArrayType()) + return true; + + while (const ConstantArrayType *ArrayT = Context.getAsConstantArrayType(T)) { + if (!ArrayT->getSize()) + return true; + + T = ArrayT->getElementType(); + } + + return false; +} + +static bool isEmpty(ASTContext &Context, const QualType &Type) { + if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) { + return ClassDecl->isEmpty(); + } + return isIncompleteOrZeroLengthArrayType(Context, Type); +} + +void ProTypeMemberInitCheck::checkMissingMemberInitializer( + ASTContext &Context, const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor) { + bool IsUnion = ClassDecl.isUnion(); + + if (IsUnion && ClassDecl.hasInClassInitializer()) + return; + + // Gather all fields (direct and indirect) that need to be initialized. + SmallPtrSet FieldsToInit; + forEachField(ClassDecl, ClassDecl.fields(), false, [&](const FieldDecl *F) { + if (!F->hasInClassInitializer() && + utils::type_traits::isTriviallyDefaultConstructible(F->getType(), + Context) && + !isEmpty(Context, F->getType()) && !F->isUnnamedBitfield()) + FieldsToInit.insert(F); + }); + if (FieldsToInit.empty()) + return; + + if (Ctor) { + for (const CXXCtorInitializer *Init : Ctor->inits()) { + // Remove any fields that were explicitly written in the initializer list + // or in-class. + if (Init->isAnyMemberInitializer() && Init->isWritten()) { + if (IsUnion) + return; // We can only initialize one member of a union. + FieldsToInit.erase(Init->getAnyMember()); + } + } + removeFieldsInitializedInBody(*Ctor->getBody(), Context, FieldsToInit); + } + + // Collect all fields in order, both direct fields and indirect fields from + // anonmyous record types. + SmallVector OrderedFields; + forEachField(ClassDecl, ClassDecl.fields(), false, + [&](const FieldDecl *F) { OrderedFields.push_back(F); }); + + // Collect all the fields we need to initialize, including indirect fields. + SmallPtrSet AllFieldsToInit; + forEachField(ClassDecl, FieldsToInit, false, + [&](const FieldDecl *F) { AllFieldsToInit.insert(F); }); + if (AllFieldsToInit.empty()) + return; + + DiagnosticBuilder Diag = + diag(Ctor ? Ctor->getLocStart() : ClassDecl.getLocation(), + IsUnion + ? "union constructor should initialize one of these fields: %0" + : "constructor does not initialize these fields: %0") + << toCommaSeparatedString(OrderedFields, AllFieldsToInit); + + // Do not propose fixes for constructors in macros since we cannot place them + // correctly. + if (Ctor && Ctor->getLocStart().isMacroID()) + return; + + // Collect all fields but only suggest a fix for the first member of unions, + // as initializing more than one union member is an error. + SmallPtrSet FieldsToFix; + forEachField(ClassDecl, FieldsToInit, true, [&](const FieldDecl *F) { + // Don't suggest fixes for enums because we don't know a good default. + // Don't suggest fixes for bitfields because in-class initialization is not + // possible. + if (!F->getType()->isEnumeralType() && !F->isBitField()) + FieldsToFix.insert(F); + }); + if (FieldsToFix.empty()) + return; + + // Use in-class initialization if possible. + if (Context.getLangOpts().CPlusPlus11) { + for (const FieldDecl *Field : FieldsToFix) { + Diag << FixItHint::CreateInsertion( + getLocationForEndOfToken(Context, Field->getSourceRange().getEnd()), + "{}"); + } + } else if (Ctor) { + // Otherwise, rewrite the constructor's initializer list. + fixInitializerList(Context, Diag, Ctor, FieldsToFix); + } +} + +void ProTypeMemberInitCheck::checkMissingBaseClassInitializer( + const ASTContext &Context, const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor) { + + // Gather any base classes that need to be initialized. + SmallVector AllBases; + SmallPtrSet BasesToInit; + for (const CXXBaseSpecifier &Base : ClassDecl.bases()) { + if (const auto *BaseClassDecl = getCanonicalRecordDecl(Base.getType())) { + AllBases.emplace_back(BaseClassDecl); + if (!BaseClassDecl->field_empty() && + utils::type_traits::isTriviallyDefaultConstructible(Base.getType(), + Context)) + BasesToInit.insert(BaseClassDecl); + } + } + + if (BasesToInit.empty()) + return; + + // Remove any bases that were explicitly written in the initializer list. + if (Ctor) { + 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..e1f3d0223 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp @@ -0,0 +1,138 @@ +//===--- 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 { + +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::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()); + + std::initializer_list> + Matchers = {{"dtor", SpecialMemberFunctionKind::Destructor}, + {"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)) { + SpecialMemberFunctionKind Kind = KV.second; + llvm::SmallVectorImpl &Members = + ClassWithSpecialMembers[ID]; + if (find(Members, Kind) == Members.end()) + Members.push_back(Kind); + } +} + +void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() { + llvm::SmallVector AllSpecialMembers = { + SpecialMemberFunctionKind::Destructor, + SpecialMemberFunctionKind::CopyConstructor, + SpecialMemberFunctionKind::CopyAssignment}; + + if (getLangOpts().CPlusPlus11) { + AllSpecialMembers.push_back(SpecialMemberFunctionKind::MoveConstructor); + AllSpecialMembers.push_back(SpecialMemberFunctionKind::MoveAssignment); + } + + for (const auto &C : ClassWithSpecialMembers) { + const auto &DefinedSpecialMembers = C.second; + + if (DefinedSpecialMembers.size() == AllSpecialMembers.size()) + continue; + + llvm::SmallVector UndefinedSpecialMembers; + std::set_difference(AllSpecialMembers.begin(), AllSpecialMembers.end(), + DefinedSpecialMembers.begin(), + DefinedSpecialMembers.end(), + std::back_inserter(UndefinedSpecialMembers)); + + diag(C.first.first, "class '%0' defines %1 but does not define %2") + << C.first.second << join(DefinedSpecialMembers, " and ") + << join(UndefinedSpecialMembers, " 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..c4e31c2f4 --- /dev/null +++ b/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h @@ -0,0 +1,97 @@ +//===--- 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) + : ClangTidyCheck(Name, Context) {} + 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, + CopyConstructor, + CopyAssignment, + MoveConstructor, + MoveAssignment + }; + + using ClassDefId = std::pair; + + using ClassDefiningSpecialMembersMap = + llvm::DenseMap>; + +private: + 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(ClassDefId LHS, ClassDefId RHS) { + if (RHS == getEmptyKey()) + return LHS == getEmptyKey(); + if (RHS == getTombstoneKey()) + return LHS == getTombstoneKey(); + return LHS == RHS; + } +}; + +} // namespace llvm + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tidy/google/AvoidCStyleCastsCheck.cpp new file mode 100644 index 000000000..10dcb342f --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.cpp @@ -0,0 +1,175 @@ +//===--- 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) { + 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; +} + +static bool pointedTypesAreEqual(QualType SourceType, QualType DestType) { + SourceType = SourceType.getNonReferenceType(); + DestType = DestType.getNonReferenceType(); + while (SourceType->isPointerType() && DestType->isPointerType()) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + } + return SourceType.getUnqualifiedType() == DestType.getUnqualifiedType(); +} + +void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CastExpr = Result.Nodes.getNodeAs("cast"); + + auto ParenRange = CharSourceRange::getTokenRange(CastExpr->getLParenLoc(), + CastExpr->getRParenLoc()); + // Ignore casts in macros. + if (ParenRange.getBegin().isMacroID() || ParenRange.getEnd().isMacroID()) + return; + + // Casting to void is an idiomatic way to mute "unused variable" and similar + // warnings. + if (CastExpr->getTypeAsWritten()->isVoidType()) + return; + + QualType SourceType = CastExpr->getSubExprAsWritten()->getType(); + QualType DestType = CastExpr->getTypeAsWritten(); + + if (SourceType == DestType) { + diag(CastExpr->getLocStart(), "redundant cast to the same type") + << FixItHint::CreateRemoval(ParenRange); + return; + } + SourceType = SourceType.getCanonicalType(); + DestType = DestType.getCanonicalType(); + if (SourceType == DestType) { + diag(CastExpr->getLocStart(), + "possibly redundant cast between typedefs of the same type"); + return; + } + + // The rest of this check is only relevant to C++. + if (!getLangOpts().CPlusPlus) + return; + // Ignore code inside extern "C" {} blocks. + if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) + .empty()) + return; + // Ignore code in .c files and headers included from them, even if they are + // compiled as C++. + if (getCurrentMainFile().endswith(".c")) + return; + // Ignore code in .c files #included in other files (which shouldn't be done, + // but people still do this for test and other purposes). + SourceManager &SM = *Result.SourceManager; + 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_builder = + diag(CastExpr->getLocStart(), "C-style casts are discouraged; use %0"); + + auto ReplaceWithCast = [&](StringRef CastType) { + diag_builder << CastType; + + const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); + std::string CastText = (CastType + "<" + DestTypeString + ">").str(); + if (!isa(SubExpr)) { + CastText.push_back('('); + diag_builder << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getLocEnd(), 0, SM, + getLangOpts()), + ")"); + } + diag_builder << FixItHint::CreateReplacement(ParenRange, CastText); + }; + + // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. + switch (CastExpr->getCastKind()) { + case CK_NoOp: + if (needsConstCast(SourceType, DestType) && + pointedTypesAreEqual(SourceType, DestType)) { + ReplaceWithCast("const_cast"); + return; + } + if (DestType->isReferenceType() && + (SourceType.getNonReferenceType() == + DestType.getNonReferenceType().withConst() || + SourceType.getNonReferenceType() == DestType.getNonReferenceType())) { + ReplaceWithCast("const_cast"); + return; + } + // 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())) { + ReplaceWithCast("static_cast"); + return; + } + break; + case CK_BitCast: + // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. + if (!needsConstCast(SourceType, DestType)) { + ReplaceWithCast("reinterpret_cast"); + return; + } + break; + default: + break; + } + + diag_builder << "static_cast/const_cast/reinterpret_cast"; +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/AvoidCStyleCastsCheck.h b/clang-tidy/google/AvoidCStyleCastsCheck.h new file mode 100644 index 000000000..ea7e34caa --- /dev/null +++ b/clang-tidy/google/AvoidCStyleCastsCheck.h @@ -0,0 +1,42 @@ +//===--- AvoidCStyleCastsCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Finds usages of C-style casts. +/// +/// https://google.github.io/styleguide/cppguide.html#Casting +/// +/// Corresponding cpplint.py check name: 'readability/casting'. +/// +/// This check is similar to `-Wold-style-cast`, but it suggests automated fixes +/// in some cases. The reported locations should not be different from the +/// ones generated by `-Wold-style-cast`. +class AvoidCStyleCastsCheck : public ClangTidyCheck { +public: + AvoidCStyleCastsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H diff --git a/clang-tidy/google/CMakeLists.txt b/clang-tidy/google/CMakeLists.txt new file mode 100644 index 000000000..efe3b3cde --- /dev/null +++ b/clang-tidy/google/CMakeLists.txt @@ -0,0 +1,27 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyGoogleModule + AvoidCStyleCastsCheck.cpp + DefaultArgumentsCheck.cpp + ExplicitConstructorCheck.cpp + ExplicitMakePairCheck.cpp + GlobalNamesInHeadersCheck.cpp + GoogleTidyModule.cpp + IntegerTypesCheck.cpp + MemsetZeroLengthCheck.cpp + NonConstReferences.cpp + OverloadedUnaryAndCheck.cpp + StringReferenceMemberCheck.cpp + TodoCommentCheck.cpp + UnnamedNamespaceInHeaderCheck.cpp + UsingNamespaceDirectiveCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/google/DefaultArgumentsCheck.cpp b/clang-tidy/google/DefaultArgumentsCheck.cpp new file mode 100644 index 000000000..ccbd870a6 --- /dev/null +++ b/clang-tidy/google/DefaultArgumentsCheck.cpp @@ -0,0 +1,36 @@ +//===--- DefaultArgumentsCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DefaultArgumentsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +void DefaultArgumentsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxMethodDecl(anyOf(isOverride(), isVirtual()), + hasAnyParameter(parmVarDecl(hasInitializer(expr())))) + .bind("Decl"), + this); +} + +void DefaultArgumentsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("Decl"); + diag(MatchedDecl->getLocation(), + "default arguments on virtual or override methods are prohibited"); +} + +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/DefaultArgumentsCheck.h b/clang-tidy/google/DefaultArgumentsCheck.h new file mode 100644 index 000000000..1457a093d --- /dev/null +++ b/clang-tidy/google/DefaultArgumentsCheck.h @@ -0,0 +1,34 @@ +//===--- DefaultArgumentsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_DEFAULT_ARGUMENTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_DEFAULT_ARGUMENTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { + +/// Checks that default parameters are not given for virtual methods. +/// +/// See https://google.github.io/styleguide/cppguide.html#Default_Arguments +class DefaultArgumentsCheck : public ClangTidyCheck { +public: + DefaultArgumentsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_DEFAULT_ARGUMENTS_H diff --git a/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tidy/google/ExplicitConstructorCheck.cpp new file mode 100644 index 000000000..220d31c36 --- /dev/null +++ b/clang-tidy/google/ExplicitConstructorCheck.cpp @@ -0,0 +1,153 @@ +//===--- 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(isInstantiated())).bind("ctor"), + this); + Finder->addMatcher( + cxxConversionDecl(unless(anyOf(isExplicit(), // Already marked explicit. + isImplicit(), // Compiler-generated. + 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")) { + 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"); + // Do not be confused: isExplicit means 'explicit' keyword is present, + // isImplicit means that it's a compiler-generated constructor. + if (Ctor->isOutOfLine() || Ctor->isImplicit() || Ctor->isDeleted() || + Ctor->getNumParams() == 0 || Ctor->getMinRequiredArguments() > 1) + return; + + bool takesInitializerList = isStdInitializerList( + Ctor->getParamDecl(0)->getType().getNonReferenceType()); + if (Ctor->isExplicit() && + (Ctor->isCopyOrMoveConstructor() || takesInitializerList)) { + auto isKWExplicit = [](const Token &Tok) { + return Tok.is(tok::raw_identifier) && + Tok.getRawIdentifier() == "explicit"; + }; + SourceRange ExplicitTokenRange = + FindToken(*Result.SourceManager, getLangOpts(), + Ctor->getOuterLocStart(), Ctor->getLocEnd(), isKWExplicit); + StringRef ConstructorDescription; + if (Ctor->isMoveConstructor()) + ConstructorDescription = "move"; + else if (Ctor->isCopyConstructor()) + ConstructorDescription = "copy"; + else + ConstructorDescription = "initializer-list"; + + auto Diag = diag(Ctor->getLocation(), + "%0 constructor should not be declared explicit") + << ConstructorDescription; + if (ExplicitTokenRange.isValid()) { + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ExplicitTokenRange)); + } + return; + } + + if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() || + takesInitializerList) + return; + + bool SingleArgument = + Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack(); + SourceLocation Loc = Ctor->getLocation(); + diag(Loc, WarningMessage) + << (SingleArgument + ? "single-argument constructors" + : "constructors that are callable with a single argument") + << FixItHint::CreateInsertion(Loc, "explicit "); +} + +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/ExplicitConstructorCheck.h b/clang-tidy/google/ExplicitConstructorCheck.h new file mode 100644 index 000000000..81e667902 --- /dev/null +++ b/clang-tidy/google/ExplicitConstructorCheck.h @@ -0,0 +1,34 @@ +//===--- ExplicitConstructorCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { + +/// Checks that all single-argument constructors are explicit. +/// +/// See https://google.github.io/styleguide/cppguide.html#Explicit_Constructors +class ExplicitConstructorCheck : public ClangTidyCheck { +public: + ExplicitConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITCONSTRUCTORCHECK_H diff --git a/clang-tidy/google/ExplicitMakePairCheck.cpp b/clang-tidy/google/ExplicitMakePairCheck.cpp new file mode 100644 index 000000000..6c2f0a368 --- /dev/null +++ b/clang-tidy/google/ExplicitMakePairCheck.cpp @@ -0,0 +1,78 @@ +//===--- ExplicitMakePairCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExplicitMakePairCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { + return Node.hasExplicitTemplateArgs(); +} +} // namespace + +namespace tidy { +namespace google { +namespace build { + +void ExplicitMakePairCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for std::make_pair with explicit template args. Ignore calls in + // templates. + Finder->addMatcher( + callExpr(unless(isInTemplateInstantiation()), + callee(expr(ignoringParenImpCasts( + declRefExpr(hasExplicitTemplateArgs(), + to(functionDecl(hasName("::std::make_pair")))) + .bind("declref"))))) + .bind("call"), + this); +} + +void ExplicitMakePairCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *DeclRef = Result.Nodes.getNodeAs("declref"); + + // Sanity check: The use might have overriden ::std::make_pair. + if (Call->getNumArgs() != 2) + return; + + const Expr *Arg0 = Call->getArg(0)->IgnoreParenImpCasts(); + const Expr *Arg1 = Call->getArg(1)->IgnoreParenImpCasts(); + + // If types don't match, we suggest replacing with std::pair and explicit + // template arguments. Otherwise just remove the template arguments from + // make_pair. + if (Arg0->getType() != Call->getArg(0)->getType() || + Arg1->getType() != Call->getArg(1)->getType()) { + diag(Call->getLocStart(), "for C++11-compatibility, use pair directly") + << FixItHint::CreateReplacement( + SourceRange(DeclRef->getLocStart(), DeclRef->getLAngleLoc()), + "std::pair<"); + } else { + diag(Call->getLocStart(), + "for C++11-compatibility, omit template arguments from make_pair") + << FixItHint::CreateRemoval( + SourceRange(DeclRef->getLAngleLoc(), DeclRef->getRAngleLoc())); + } +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/ExplicitMakePairCheck.h b/clang-tidy/google/ExplicitMakePairCheck.h new file mode 100644 index 000000000..a29825f3c --- /dev/null +++ b/clang-tidy/google/ExplicitMakePairCheck.h @@ -0,0 +1,39 @@ +//===--- ExplicitMakePairCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Check that `make_pair`'s template arguments are deduced. +/// +/// G++ 4.6 in C++11 mode fails badly if `make_pair`'s template arguments are +/// specified explicitly, and such use isn't intended in any case. +/// +/// Corresponding cpplint.py check name: 'build/explicit_make_pair'. +class ExplicitMakePairCheck : public ClangTidyCheck { +public: + ExplicitMakePairCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_EXPLICITMAKEPAIRCHECK_H diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.cpp b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp new file mode 100644 index 000000000..71049d993 --- /dev/null +++ b/clang-tidy/google/GlobalNamesInHeadersCheck.cpp @@ -0,0 +1,80 @@ +//===--- GlobalNamesInHeadersCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "GlobalNamesInHeadersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +GlobalNamesInHeadersCheck::GlobalNamesInHeadersCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", "h")) { + if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ',')) { + llvm::errs() << "Invalid header file extension: " + << RawStringHeaderFileExtensions << "\n"; + } +} + +void GlobalNamesInHeadersCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +void GlobalNamesInHeadersCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + Finder->addMatcher(decl(anyOf(usingDecl(), usingDirectiveDecl()), + hasDeclContext(translationUnitDecl())) + .bind("using_decl"), + this); +} + +void GlobalNamesInHeadersCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("using_decl"); + // If it comes from a macro, we'll assume it is fine. + if (D->getLocStart().isMacroID()) + return; + + // Ignore if it comes from the "main" file ... + if (Result.SourceManager->isInMainFile( + Result.SourceManager->getExpansionLoc(D->getLocStart()))) { + // unless that file is a header. + if (!utils::isSpellingLocInHeaderFile( + D->getLocStart(), *Result.SourceManager, HeaderFileExtensions)) + return; + } + + if (const auto *UsingDirective = dyn_cast(D)) { + if (UsingDirective->getNominatedNamespace()->isAnonymousNamespace()) { + // Anynoumous namespaces inject a using directive into the AST to import + // the names into the containing namespace. + // We should not have them in headers, but there is another warning for + // that. + return; + } + } + + diag(D->getLocStart(), + "using declarations in the global namespace in headers are prohibited"); +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/GlobalNamesInHeadersCheck.h b/clang-tidy/google/GlobalNamesInHeadersCheck.h new file mode 100644 index 000000000..79a6e2854 --- /dev/null +++ b/clang-tidy/google/GlobalNamesInHeadersCheck.h @@ -0,0 +1,47 @@ +//===--- GlobalNamesInHeadersCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Flag global namespace pollution in header files. +/// Right now it only triggers on using declarations and directives. +/// +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions +/// of header files (the filename extensions should not contain "." prefix). +/// "h" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +class GlobalNamesInHeadersCheck : public ClangTidyCheck { +public: + GlobalNamesInHeadersCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_GLOBALNAMESINHEADERSCHECK_H diff --git a/clang-tidy/google/GoogleTidyModule.cpp b/clang-tidy/google/GoogleTidyModule.cpp new file mode 100644 index 000000000..e9b4bedf0 --- /dev/null +++ b/clang-tidy/google/GoogleTidyModule.cpp @@ -0,0 +1,102 @@ +//===--- GoogleTidyModule.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../readability/BracesAroundStatementsCheck.h" +#include "../readability/FunctionSizeCheck.h" +#include "../readability/NamespaceCommentCheck.h" +#include "../readability/RedundantSmartptrGetCheck.h" +#include "AvoidCStyleCastsCheck.h" +#include "DefaultArgumentsCheck.h" +#include "ExplicitConstructorCheck.h" +#include "ExplicitMakePairCheck.h" +#include "GlobalNamesInHeadersCheck.h" +#include "IntegerTypesCheck.h" +#include "MemsetZeroLengthCheck.h" +#include "NonConstReferences.h" +#include "OverloadedUnaryAndCheck.h" +#include "StringReferenceMemberCheck.h" +#include "TodoCommentCheck.h" +#include "UnnamedNamespaceInHeaderCheck.h" +#include "UsingNamespaceDirectiveCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { + +class GoogleModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "google-build-explicit-make-pair"); + CheckFactories.registerCheck( + "google-build-namespaces"); + CheckFactories.registerCheck( + "google-build-using-namespace"); + CheckFactories.registerCheck( + "google-default-arguments"); + CheckFactories.registerCheck( + "google-explicit-constructor"); + CheckFactories.registerCheck( + "google-runtime-int"); + CheckFactories.registerCheck( + "google-runtime-operator"); + CheckFactories.registerCheck( + "google-runtime-references"); + CheckFactories.registerCheck( + "google-runtime-member-string-references"); + CheckFactories.registerCheck( + "google-runtime-memset"); + CheckFactories.registerCheck( + "google-readability-casting"); + CheckFactories.registerCheck( + "google-readability-todo"); + CheckFactories + .registerCheck( + "google-readability-braces-around-statements"); + CheckFactories.registerCheck( + "google-global-names-in-headers"); + CheckFactories.registerCheck( + "google-readability-function-size"); + CheckFactories + .registerCheck( + "google-readability-namespace-comments"); + CheckFactories + .registerCheck( + "google-readability-redundant-smartptr-get"); + } + + ClangTidyOptions getModuleOptions() override { + ClangTidyOptions Options; + auto &Opts = Options.CheckOptions; + Opts["google-readability-braces-around-statements.ShortStatementLines"] = + "1"; + Opts["google-readability-function-size.StatementThreshold"] = "800"; + Opts["google-readability-namespace-comments.ShortNamespaceLines"] = "10"; + Opts["google-readability-namespace-comments.SpacesBeforeComments"] = "2"; + return Options; + } +}; + +// Register the GoogleTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("google-module", + "Adds Google lint checks."); + +} // namespace google + +// This anchor is used to force the linker to link in the generated object file +// and thus register the GoogleModule. +volatile int GoogleModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/IntegerTypesCheck.cpp b/clang-tidy/google/IntegerTypesCheck.cpp new file mode 100644 index 000000000..f6c917ca2 --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.cpp @@ -0,0 +1,144 @@ +//===--- IntegerTypesCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IntegerTypesCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/CharInfo.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Lexer.h" + +namespace clang { + +using namespace ast_matchers; + +static Token getTokenAtLoc(SourceLocation Loc, + const MatchFinder::MatchResult &MatchResult, + IdentifierTable &IdentTable) { + Token Tok; + if (Lexer::getRawToken(Loc, Tok, *MatchResult.SourceManager, + MatchResult.Context->getLangOpts(), false)) + return Tok; + + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &Info = IdentTable.get(Tok.getRawIdentifier()); + Tok.setIdentifierInfo(&Info); + Tok.setKind(Info.getTokenID()); + } + return Tok; +} + +namespace tidy { +namespace google { +namespace runtime { + +IntegerTypesCheck::IntegerTypesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UnsignedTypePrefix(Options.get("UnsignedTypePrefix", "uint")), + SignedTypePrefix(Options.get("SignedTypePrefix", "int")), + TypeSuffix(Options.get("TypeSuffix", "")) {} + +void IntegerTypesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UnsignedTypePrefix", UnsignedTypePrefix); + Options.store(Opts, "SignedTypePrefix", SignedTypePrefix); + Options.store(Opts, "TypeSuffix", TypeSuffix); +} + +void IntegerTypesCheck::registerMatchers(MatchFinder *Finder) { + // Find all TypeLocs. The relevant Style Guide rule only applies to C++. + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher(typeLoc(loc(isInteger())).bind("tl"), this); + IdentTable = llvm::make_unique(getLangOpts()); +} + +void IntegerTypesCheck::check(const MatchFinder::MatchResult &Result) { + auto TL = *Result.Nodes.getNodeAs("tl"); + SourceLocation Loc = TL.getLocStart(); + + if (Loc.isInvalid() || Loc.isMacroID()) + return; + + // Look through qualification. + if (auto QualLoc = TL.getAs()) + TL = QualLoc.getUnqualifiedLoc(); + + auto BuiltinLoc = TL.getAs(); + if (!BuiltinLoc) + return; + + Token Tok = getTokenAtLoc(Loc, Result, *IdentTable); + // Ensure the location actually points to one of the builting integral type + // names we're interested in. Otherwise, we might be getting this match from + // implicit code (e.g. an implicit assignment operator of a class containing + // an array of non-POD types). + if (!Tok.isOneOf(tok::kw_short, tok::kw_long, tok::kw_unsigned, + tok::kw_signed)) + return; + + bool IsSigned; + unsigned Width; + const TargetInfo &TargetInfo = Result.Context->getTargetInfo(); + + // Look for uses of short, long, long long and their unsigned versions. + switch (BuiltinLoc.getTypePtr()->getKind()) { + case BuiltinType::Short: + Width = TargetInfo.getShortWidth(); + IsSigned = true; + break; + case BuiltinType::Long: + Width = TargetInfo.getLongWidth(); + IsSigned = true; + break; + case BuiltinType::LongLong: + Width = TargetInfo.getLongLongWidth(); + IsSigned = true; + break; + case BuiltinType::UShort: + Width = TargetInfo.getShortWidth(); + IsSigned = false; + break; + case BuiltinType::ULong: + Width = TargetInfo.getLongWidth(); + IsSigned = false; + break; + case BuiltinType::ULongLong: + Width = TargetInfo.getLongLongWidth(); + IsSigned = false; + break; + default: + return; + } + + // We allow "unsigned short port" as that's reasonably common and required by + // the sockets API. + const StringRef Port = "unsigned short port"; + const char *Data = Result.SourceManager->getCharacterData(Loc); + if (!std::strncmp(Data, Port.data(), Port.size()) && + !isIdentifierBody(Data[Port.size()])) + return; + + std::string Replacement = + ((IsSigned ? SignedTypePrefix : UnsignedTypePrefix) + Twine(Width) + + TypeSuffix) + .str(); + + // We don't add a fix-it as changing the type can easily break code, + // e.g. when a function requires a 'long' argument on all platforms. + // QualTypes are printed with implicit quotes. + diag(Loc, "consider replacing %0 with '%1'") << BuiltinLoc.getType() + << Replacement; +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/IntegerTypesCheck.h b/clang-tidy/google/IntegerTypesCheck.h new file mode 100644 index 000000000..8d8f9038d --- /dev/null +++ b/clang-tidy/google/IntegerTypesCheck.h @@ -0,0 +1,49 @@ +//===--- IntegerTypesCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H + +#include "../ClangTidy.h" + +#include + +namespace clang { + +class IdentifierTable; + +namespace tidy { +namespace google { +namespace runtime { + +/// Finds uses of `short`, `long` and `long long` and suggest replacing them +/// with `u?intXX(_t)?`. +/// +/// Correspondig cpplint.py check: 'runtime/int'. +class IntegerTypesCheck : public ClangTidyCheck { +public: + IntegerTypesCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + +private: + const std::string UnsignedTypePrefix; + const std::string SignedTypePrefix; + const std::string TypeSuffix; + + std::unique_ptr IdentTable; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_INTEGERTYPESCHECK_H diff --git a/clang-tidy/google/MemsetZeroLengthCheck.cpp b/clang-tidy/google/MemsetZeroLengthCheck.cpp new file mode 100644 index 000000000..fa87fab20 --- /dev/null +++ b/clang-tidy/google/MemsetZeroLengthCheck.cpp @@ -0,0 +1,96 @@ +//===--- MemsetZeroLengthCheck.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 "MemsetZeroLengthCheck.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 runtime { + +void MemsetZeroLengthCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Look for memset(x, y, 0) as those is most likely an argument swap. + // TODO: Also handle other standard functions that suffer from the same + // problem, e.g. memchr. + Finder->addMatcher(callExpr(callee(functionDecl(hasName("::memset"))), + argumentCountIs(3), + unless(isInTemplateInstantiation())) + .bind("decl"), + this); +} + +/// \brief Get a StringRef representing a SourceRange. +static StringRef getAsString(const MatchFinder::MatchResult &Result, + SourceRange R) { + const SourceManager &SM = *Result.SourceManager; + // Don't even try to resolve macro or include contraptions. Not worth emitting + // a fixit for. + if (R.getBegin().isMacroID() || + !SM.isWrittenInSameFile(R.getBegin(), R.getEnd())) + return StringRef(); + + const char *Begin = SM.getCharacterData(R.getBegin()); + const char *End = SM.getCharacterData(Lexer::getLocForEndOfToken( + R.getEnd(), 0, SM, Result.Context->getLangOpts())); + + return StringRef(Begin, End - Begin); +} + +void MemsetZeroLengthCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("decl"); + + // Note, this is: + // void *memset(void *buffer, int fill_char, size_t byte_count); + // Arg1 is fill_char, Arg2 is byte_count. + const Expr *Arg1 = Call->getArg(1); + const Expr *Arg2 = Call->getArg(2); + + // Return if `byte_count` is not zero at compile time. + llvm::APSInt Value1, Value2; + if (Arg2->isValueDependent() || + !Arg2->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 (!Arg1->isValueDependent() && + Arg1->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"); + SourceRange LHSRange = Arg1->getSourceRange(); + SourceRange RHSRange = Arg2->getSourceRange(); + StringRef RHSString = getAsString(Result, RHSRange); + StringRef LHSString = getAsString(Result, LHSRange); + if (LHSString.empty() || RHSString.empty()) + return; + + D << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(LHSRange), + RHSString) + << FixItHint::CreateReplacement(CharSourceRange::getTokenRange(RHSRange), + LHSString); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/MemsetZeroLengthCheck.h b/clang-tidy/google/MemsetZeroLengthCheck.h new file mode 100644 index 000000000..57c7aec74 --- /dev/null +++ b/clang-tidy/google/MemsetZeroLengthCheck.h @@ -0,0 +1,39 @@ +//===--- MemsetZeroLengthCheck.h - clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// 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_MEMSETZEROLENGTHCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_MEMSETZEROLENGTHCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds calls to memset with a literal zero in the length argument. +/// +/// This is most likely unintended and the length and value arguments are +/// swapped. +/// +/// Corresponding cpplint.py check name: 'runtime/memset'. +class MemsetZeroLengthCheck : public ClangTidyCheck { +public: + MemsetZeroLengthCheck(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_MEMSETZEROLENGTHCHECK_H diff --git a/clang-tidy/google/NonConstReferences.cpp b/clang-tidy/google/NonConstReferences.cpp new file mode 100644 index 000000000..856bc5bc2 --- /dev/null +++ b/clang-tidy/google/NonConstReferences.cpp @@ -0,0 +1,149 @@ +//===--- NonConstReferences.cpp - clang-tidy --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonConstReferences.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/DeclBase.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +NonConstReferences::NonConstReferences(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WhiteListTypes( + utils::options::parseStringList(Options.get("WhiteListTypes", ""))) {} + +void NonConstReferences::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WhiteListTypes", + utils::options::serializeStringList(WhiteListTypes)); +} + +void NonConstReferences::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + parmVarDecl( + unless(isInstantiated()), + hasType(references( + qualType(unless(isConstQualified())).bind("referenced_type"))), + unless(hasType(rValueReferenceType()))) + .bind("param"), + this); +} + +void NonConstReferences::check(const MatchFinder::MatchResult &Result) { + const auto *Parameter = Result.Nodes.getNodeAs("param"); + const auto *Function = + dyn_cast_or_null(Parameter->getParentFunctionOrMethod()); + + if (Function == nullptr || Function->isImplicit()) + return; + + if (!Function->isCanonicalDecl()) + return; + + if (const auto *Method = dyn_cast(Function)) { + // Don't warn on implementations of an interface using references. + if (Method->begin_overridden_methods() != Method->end_overridden_methods()) + return; + // Don't warn on lambdas, as they frequently have to conform to the + // interface defined elsewhere. + if (Method->getParent()->isLambda()) + return; + } + + auto ReferencedType = *Result.Nodes.getNodeAs("referenced_type"); + + if (std::find_if(WhiteListTypes.begin(), WhiteListTypes.end(), + [&](llvm::StringRef WhiteListType) { + return ReferencedType.getCanonicalType().getAsString( + Result.Context->getPrintingPolicy()) == + WhiteListType; + }) != WhiteListTypes.end()) + return; + + // Don't warn on function references, they shouldn't be constant. + if (ReferencedType->isFunctionProtoType()) + return; + + // Don't warn on dependent types in templates. + if (ReferencedType->isDependentType()) + return; + + if (Function->isOverloadedOperator()) { + switch (Function->getOverloadedOperator()) { + case clang::OO_LessLess: + case clang::OO_PlusPlus: + case clang::OO_MinusMinus: + case clang::OO_PlusEqual: + case clang::OO_MinusEqual: + case clang::OO_StarEqual: + case clang::OO_SlashEqual: + case clang::OO_PercentEqual: + case clang::OO_LessLessEqual: + case clang::OO_GreaterGreaterEqual: + case clang::OO_PipeEqual: + case clang::OO_CaretEqual: + case clang::OO_AmpEqual: + // Don't warn on the first parameter of operator<<(Stream&, ...), + // operator++, operator-- and operation+assignment operators. + if (Function->getParamDecl(0) == Parameter) + return; + break; + case clang::OO_GreaterGreater: { + auto isNonConstRef = [](clang::QualType T) { + return T->isReferenceType() && + !T.getNonReferenceType().isConstQualified(); + }; + // Don't warn on parameters of stream extractors: + // Stream& operator>>(Stream&, Value&); + // Both parameters should be non-const references by convention. + if (isNonConstRef(Function->getParamDecl(0)->getType()) && + (Function->getNumParams() < 2 || // E.g. member operator>>. + isNonConstRef(Function->getParamDecl(1)->getType())) && + isNonConstRef(Function->getReturnType())) + return; + break; + } + default: + break; + } + } + + // Some functions use references to comply with established standards. + if (Function->getDeclName().isIdentifier() && Function->getName() == "swap") + return; + + // iostream parameters are typically passed by non-const reference. + if (StringRef(ReferencedType.getAsString()).endswith("stream")) + return; + + if (Parameter->getName().empty()) { + diag(Parameter->getLocation(), "non-const reference parameter at index %0, " + "make it const or use a pointer") + << Parameter->getFunctionScopeIndex(); + } else { + diag(Parameter->getLocation(), + "non-const reference parameter %0, make it const or use a pointer") + << Parameter; + } +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/NonConstReferences.h b/clang-tidy/google/NonConstReferences.h new file mode 100644 index 000000000..a665813ff --- /dev/null +++ b/clang-tidy/google/NonConstReferences.h @@ -0,0 +1,39 @@ +//===--- NonConstReferences.h - clang-tidy ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_NON_CONST_REFERENCES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_NON_CONST_REFERENCES_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// \brief Checks the usage of non-constant references in function parameters. +/// +/// https://google.github.io/styleguide/cppguide.html#Reference_Arguments +class NonConstReferences : public ClangTidyCheck { +public: + NonConstReferences(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::vector WhiteListTypes; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_NON_CONST_REFERENCES_H diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.cpp b/clang-tidy/google/OverloadedUnaryAndCheck.cpp new file mode 100644 index 000000000..84abb5fd7 --- /dev/null +++ b/clang-tidy/google/OverloadedUnaryAndCheck.cpp @@ -0,0 +1,53 @@ +//===--- OverloadedUnaryAndCheck.cpp - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OverloadedUnaryAndCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void OverloadedUnaryAndCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Match unary methods that overload operator&. + Finder->addMatcher( + cxxMethodDecl(parameterCountIs(0), hasOverloadedOperatorName("&")) + .bind("overload"), + this); + // Also match freestanding unary operator& overloads. Be careful not to match + // binary methods. + Finder->addMatcher( + functionDecl(allOf( + unless(cxxMethodDecl()), + functionDecl(parameterCountIs(1), hasOverloadedOperatorName("&")) + .bind("overload"))), + this); +} + +void OverloadedUnaryAndCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Decl = Result.Nodes.getNodeAs("overload"); + diag(Decl->getLocStart(), + "do not overload unary operator&, it is dangerous."); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/OverloadedUnaryAndCheck.h b/clang-tidy/google/OverloadedUnaryAndCheck.h new file mode 100644 index 000000000..5492eba21 --- /dev/null +++ b/clang-tidy/google/OverloadedUnaryAndCheck.h @@ -0,0 +1,38 @@ +//===--- OverloadedUnaryAndCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds overloads of unary `operator &`. +/// +/// https://google.github.io/styleguide/cppguide.html#Operator_Overloading +/// +/// Corresponding cpplint.py check name: 'runtime/operator'. +class OverloadedUnaryAndCheck : public ClangTidyCheck { +public: + OverloadedUnaryAndCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OVERLOADEDUNARYANDCHECK_H diff --git a/clang-tidy/google/StringReferenceMemberCheck.cpp b/clang-tidy/google/StringReferenceMemberCheck.cpp new file mode 100644 index 000000000..36d979b61 --- /dev/null +++ b/clang-tidy/google/StringReferenceMemberCheck.cpp @@ -0,0 +1,51 @@ +//===--- StringReferenceMemberCheck.cpp - clang-tidy ------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringReferenceMemberCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +void StringReferenceMemberCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for const references to std::string or ::string. + auto String = anyOf(recordDecl(hasName("::std::basic_string")), + recordDecl(hasName("::string"))); + auto ConstString = qualType(isConstQualified(), hasDeclaration(String)); + + // Ignore members in template instantiations. + Finder->addMatcher( + fieldDecl(hasType(references(ConstString)), unless(isInstantiated())) + .bind("member"), + this); +} + +void StringReferenceMemberCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Member = Result.Nodes.getNodeAs("member"); + diag(Member->getLocStart(), "const string& members are dangerous; it is much " + "better to use alternatives, such as pointers or " + "simple constants"); +} + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/StringReferenceMemberCheck.h b/clang-tidy/google/StringReferenceMemberCheck.h new file mode 100644 index 000000000..5a39fd9b3 --- /dev/null +++ b/clang-tidy/google/StringReferenceMemberCheck.h @@ -0,0 +1,54 @@ +//===--- StringReferenceMemberCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace runtime { + +/// Finds members of type `const string&`. +/// +/// const string reference members are generally considered unsafe as they can +/// be created from a temporary quite easily. +/// +/// \code +/// struct S { +/// S(const string &Str) : Str(Str) {} +/// const string &Str; +/// }; +/// S instance("string"); +/// \endcode +/// +/// In the constructor call a string temporary is created from `const char *` +/// and destroyed immediately after the call. This leaves around a dangling +/// reference. +/// +/// This check emit warnings for both `std::string` and `::string` const +/// reference members. +/// +/// Corresponding cpplint.py check name: 'runtime/member_string_reference'. +class StringReferenceMemberCheck : public ClangTidyCheck { +public: + StringReferenceMemberCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace runtime +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_STRINGREFERENCEMEMBERCHECK_H diff --git a/clang-tidy/google/TodoCommentCheck.cpp b/clang-tidy/google/TodoCommentCheck.cpp new file mode 100644 index 000000000..f1c79ce6b --- /dev/null +++ b/clang-tidy/google/TodoCommentCheck.cpp @@ -0,0 +1,66 @@ +//===--- TodoCommentCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TodoCommentCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +class TodoCommentCheck::TodoCommentHandler : public CommentHandler { +public: + TodoCommentHandler(TodoCommentCheck &Check, llvm::Optional User) + : Check(Check), User(User ? *User : "unknown"), + TodoMatch("^// *TODO *(\\(.*\\))?:?( )?(.*)$") {} + + bool HandleComment(Preprocessor &PP, SourceRange Range) override { + StringRef Text = + Lexer::getSourceText(CharSourceRange::getCharRange(Range), + PP.getSourceManager(), PP.getLangOpts()); + + SmallVector Matches; + if (!TodoMatch.match(Text, &Matches)) + return false; + + StringRef Username = Matches[1]; + StringRef Comment = Matches[3]; + + if (!Username.empty()) + return false; + + std::string NewText = ("// TODO(" + Twine(User) + "): " + Comment).str(); + + Check.diag(Range.getBegin(), "missing username/bug in TODO") + << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Range), + NewText); + return false; + } + +private: + TodoCommentCheck &Check; + std::string User; + llvm::Regex TodoMatch; +}; + +TodoCommentCheck::TodoCommentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Handler(llvm::make_unique( + *this, Context->getOptions().User)) {} + +void TodoCommentCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addCommentHandler(Handler.get()); +} + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/TodoCommentCheck.h b/clang-tidy/google/TodoCommentCheck.h new file mode 100644 index 000000000..dbdc3668c --- /dev/null +++ b/clang-tidy/google/TodoCommentCheck.h @@ -0,0 +1,38 @@ +//===--- TodoCommentCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace google { +namespace readability { + +/// Finds TODO comments without a username or bug number. +/// +/// Corresponding cpplint.py check: 'readability/todo' +class TodoCommentCheck : public ClangTidyCheck { +public: + TodoCommentCheck(StringRef Name, ClangTidyContext *Context); + void registerPPCallbacks(CompilerInstance &Compiler) override; + +private: + class TodoCommentHandler; + std::unique_ptr Handler; +}; + +} // namespace readability +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_TODOCOMMENTCHECK_H diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp new file mode 100644 index 000000000..3e76bdb81 --- /dev/null +++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.cpp @@ -0,0 +1,63 @@ +//===--- UnnamedNamespaceInHeaderCheck.cpp - clang-tidy ---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnamedNamespaceInHeaderCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +UnnamedNamespaceInHeaderCheck::UnnamedNamespaceInHeaderCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", "h,hh,hpp,hxx")) { + if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ',')) { + llvm::errs() << "Invalid header file extension: " + << RawStringHeaderFileExtensions << "\n"; + } +} + +void UnnamedNamespaceInHeaderCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +void UnnamedNamespaceInHeaderCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(namespaceDecl(isAnonymous()).bind("anonymousNamespace"), + this); +} + +void UnnamedNamespaceInHeaderCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *N = Result.Nodes.getNodeAs("anonymousNamespace"); + SourceLocation Loc = N->getLocStart(); + if (!Loc.isValid()) + return; + + if (utils::isPresumedLocInHeaderFile(Loc, *Result.SourceManager, + HeaderFileExtensions)) + diag(Loc, "do not use unnamed namespaces in header files"); +} + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h new file mode 100644 index 000000000..4d310f570 --- /dev/null +++ b/clang-tidy/google/UnnamedNamespaceInHeaderCheck.h @@ -0,0 +1,50 @@ +//===--- UnnamedNamespaceInHeaderCheck.h - clang-tidy -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace google { +namespace build { + +/// Finds anonymous namespaces in headers. +/// +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extensions should not contain "." prefix). +/// "h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +/// +/// https://google.github.io/styleguide/cppguide.html#Namespaces +/// +/// Corresponding cpplint.py check name: 'build/namespaces'. +class UnnamedNamespaceInHeaderCheck : public ClangTidyCheck { +public: + UnnamedNamespaceInHeaderCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_UNNAMEDNAMESPACEINHEADERCHECK_H diff --git a/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp new file mode 100644 index 000000000..6fc6fd3f6 --- /dev/null +++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.cpp @@ -0,0 +1,46 @@ +//===--- 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; + + 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. +} + +} // 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..a36f3803b --- /dev/null +++ b/clang-tidy/google/UsingNamespaceDirectiveCheck.h @@ -0,0 +1,48 @@ +//===--- 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; +}; + +} // namespace build +} // namespace google +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_USINGNAMESPACEDIRECTIVECHECK_H diff --git a/clang-tidy/llvm/CMakeLists.txt b/clang-tidy/llvm/CMakeLists.txt new file mode 100644 index 000000000..ce69c05f3 --- /dev/null +++ b/clang-tidy/llvm/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyLLVMModule + HeaderGuardCheck.cpp + IncludeOrderCheck.cpp + LLVMTidyModule.cpp + TwineLocalCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/llvm/HeaderGuardCheck.cpp b/clang-tidy/llvm/HeaderGuardCheck.cpp new file mode 100644 index 000000000..6e0c2cb1e --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.cpp @@ -0,0 +1,68 @@ +//===--- HeaderGuardCheck.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderGuardCheck.h" + +namespace clang { +namespace tidy { +namespace llvm { + +LLVMHeaderGuardCheck::LLVMHeaderGuardCheck(StringRef Name, + ClangTidyContext *Context) + : HeaderGuardCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) { + utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ','); +} + +void LLVMHeaderGuardCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +bool LLVMHeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { + return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); +} + +std::string LLVMHeaderGuardCheck::getHeaderGuard(StringRef Filename, + StringRef OldGuard) { + std::string Guard = tooling::getAbsolutePath(Filename); + + // Sanitize the path. There are some rules for compatibility with the historic + // style in include/llvm and include/clang which we want to preserve. + + // We don't want _INCLUDE_ in our guards. + size_t PosInclude = Guard.rfind("include/"); + if (PosInclude != StringRef::npos) + Guard = Guard.substr(PosInclude + std::strlen("include/")); + + // For clang we drop the _TOOLS_. + size_t PosToolsClang = Guard.rfind("tools/clang/"); + if (PosToolsClang != StringRef::npos) + Guard = Guard.substr(PosToolsClang + std::strlen("tools/")); + + // The remainder is LLVM_FULL_PATH_TO_HEADER_H + size_t PosLLVM = Guard.rfind("llvm/"); + if (PosLLVM != StringRef::npos) + Guard = Guard.substr(PosLLVM); + + std::replace(Guard.begin(), Guard.end(), '/', '_'); + std::replace(Guard.begin(), Guard.end(), '.', '_'); + std::replace(Guard.begin(), Guard.end(), '-', '_'); + + // The prevalent style in clang is LLVM_CLANG_FOO_BAR_H + if (StringRef(Guard).startswith("clang")) + Guard = "LLVM_" + Guard; + + return StringRef(Guard).upper(); +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/HeaderGuardCheck.h b/clang-tidy/llvm/HeaderGuardCheck.h new file mode 100644 index 000000000..3b8ad2b2f --- /dev/null +++ b/clang-tidy/llvm/HeaderGuardCheck.h @@ -0,0 +1,46 @@ +//===--- HeaderGuardCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H + +#include "../utils/HeaderGuard.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Finds and fixes header guards that do not adhere to LLVM style. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/llvm-header-guard.html +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extension should not contain "." prefix). +/// ",h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +class LLVMHeaderGuardCheck : public utils::HeaderGuardCheck { +public: + LLVMHeaderGuardCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool shouldSuggestEndifComment(StringRef Filename) override { return false; } + bool shouldFixHeaderGuard(StringRef Filename) override; + std::string getHeaderGuard(StringRef Filename, StringRef OldGuard) override; + +private: + std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_HEADER_GUARD_CHECK_H diff --git a/clang-tidy/llvm/IncludeOrderCheck.cpp b/clang-tidy/llvm/IncludeOrderCheck.cpp new file mode 100644 index 000000000..c277a45d2 --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.cpp @@ -0,0 +1,179 @@ +//===--- IncludeOrderCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeOrderCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +#include + +namespace clang { +namespace tidy { +namespace llvm { + +namespace { +class IncludeOrderPPCallbacks : public PPCallbacks { +public: + explicit IncludeOrderPPCallbacks(ClangTidyCheck &Check, SourceManager &SM) + : LookForMainModule(true), Check(Check), SM(SM) {} + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override; + void EndOfMainFile() override; + +private: + struct IncludeDirective { + SourceLocation Loc; ///< '#' location in the include directive + CharSourceRange Range; ///< SourceRange for the file name + std::string Filename; ///< Filename as a string + bool IsAngled; ///< true if this was an include with angle brackets + bool IsMainModule; ///< true if this was the first include in a file + }; + + typedef std::vector FileIncludes; + std::map IncludeDirectives; + bool LookForMainModule; + + ClangTidyCheck &Check; + SourceManager &SM; +}; +} // namespace + +void IncludeOrderCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique( + *this, Compiler.getSourceManager())); +} + +static int getPriority(StringRef Filename, bool IsAngled, bool IsMainModule) { + // We leave the main module header at the top. + if (IsMainModule) + return 0; + + // LLVM and clang headers are in the penultimate position. + if (Filename.startswith("llvm/") || Filename.startswith("llvm-c/") || + Filename.startswith("clang/") || Filename.startswith("clang-c/")) + return 2; + + // System headers are sorted to the end. + if (IsAngled || Filename.startswith("gtest/")) + return 3; + + // Other headers are inserted between the main module header and LLVM headers. + return 1; +} + +void IncludeOrderPPCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported) { + // We recognize the first include as a special main module header and want + // to leave it in the top position. + IncludeDirective ID = {HashLoc, FilenameRange, FileName, IsAngled, false}; + if (LookForMainModule && !IsAngled) { + ID.IsMainModule = true; + LookForMainModule = false; + } + + // Bucket the include directives by the id of the file they were declared in. + IncludeDirectives[SM.getFileID(HashLoc)].push_back(std::move(ID)); +} + +void IncludeOrderPPCallbacks::EndOfMainFile() { + LookForMainModule = true; + if (IncludeDirectives.empty()) + return; + + // TODO: find duplicated includes. + + // Form blocks of includes. We don't want to sort across blocks. This also + // implicitly makes us never reorder over #defines or #if directives. + // FIXME: We should be more careful about sorting below comments as we don't + // know if the comment refers to the next include or the whole block that + // follows. + for (auto &Bucket : IncludeDirectives) { + auto &FileDirectives = Bucket.second; + std::vector Blocks(1, 0); + for (unsigned I = 1, E = FileDirectives.size(); I != E; ++I) + if (SM.getExpansionLineNumber(FileDirectives[I].Loc) != + SM.getExpansionLineNumber(FileDirectives[I - 1].Loc) + 1) + Blocks.push_back(I); + Blocks.push_back(FileDirectives.size()); // Sentinel value. + + // Get a vector of indices. + std::vector IncludeIndices; + for (unsigned I = 0, E = FileDirectives.size(); I != E; ++I) + IncludeIndices.push_back(I); + + // Sort the includes. We first sort by priority, then lexicographically. + for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) + std::sort(IncludeIndices.begin() + Blocks[BI], + IncludeIndices.begin() + Blocks[BI + 1], + [&FileDirectives](unsigned LHSI, unsigned RHSI) { + IncludeDirective &LHS = FileDirectives[LHSI]; + IncludeDirective &RHS = FileDirectives[RHSI]; + + int PriorityLHS = + getPriority(LHS.Filename, LHS.IsAngled, LHS.IsMainModule); + int PriorityRHS = + getPriority(RHS.Filename, RHS.IsAngled, RHS.IsMainModule); + + return std::tie(PriorityLHS, LHS.Filename) < + std::tie(PriorityRHS, RHS.Filename); + }); + + // Emit a warning for each block and fixits for all changes within that + // block. + for (unsigned BI = 0, BE = Blocks.size() - 1; BI != BE; ++BI) { + // Find the first include that's not in the right position. + unsigned I, E; + for (I = Blocks[BI], E = Blocks[BI + 1]; I != E; ++I) + if (IncludeIndices[I] != I) + break; + + if (I == E) + continue; + + // Emit a warning. + auto D = Check.diag(FileDirectives[I].Loc, + "#includes are not sorted properly"); + + // Emit fix-its for all following includes in this block. + for (; I != E; ++I) { + if (IncludeIndices[I] == I) + continue; + const IncludeDirective &CopyFrom = FileDirectives[IncludeIndices[I]]; + + SourceLocation FromLoc = CopyFrom.Range.getBegin(); + const char *FromData = SM.getCharacterData(FromLoc); + unsigned FromLen = std::strcspn(FromData, "\n"); + + StringRef FixedName(FromData, FromLen); + + SourceLocation ToLoc = FileDirectives[I].Range.getBegin(); + const char *ToData = SM.getCharacterData(ToLoc); + unsigned ToLen = std::strcspn(ToData, "\n"); + auto ToRange = + CharSourceRange::getCharRange(ToLoc, ToLoc.getLocWithOffset(ToLen)); + + D << FixItHint::CreateReplacement(ToRange, FixedName); + } + } + } + + IncludeDirectives.clear(); +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/IncludeOrderCheck.h b/clang-tidy/llvm/IncludeOrderCheck.h new file mode 100644 index 000000000..ad876b956 --- /dev/null +++ b/clang-tidy/llvm/IncludeOrderCheck.h @@ -0,0 +1,33 @@ +//===--- IncludeOrderCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Checks the correct order of `#includes`. +/// +/// See http://llvm.org/docs/CodingStandards.html#include-style +class IncludeOrderCheck : public ClangTidyCheck { +public: + IncludeOrderCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INCLUDE_ORDER_CHECK_H diff --git a/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tidy/llvm/LLVMTidyModule.cpp new file mode 100644 index 000000000..ea46ca938 --- /dev/null +++ b/clang-tidy/llvm/LLVMTidyModule.cpp @@ -0,0 +1,44 @@ +//===--- LLVMTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "../readability/NamespaceCommentCheck.h" +#include "HeaderGuardCheck.h" +#include "IncludeOrderCheck.h" +#include "TwineLocalCheck.h" + +namespace clang { +namespace tidy { +namespace llvm { + +class LLVMModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("llvm-header-guard"); + CheckFactories.registerCheck("llvm-include-order"); + CheckFactories.registerCheck( + "llvm-namespace-comment"); + CheckFactories.registerCheck("llvm-twine-local"); + } +}; + +// Register the LLVMTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("llvm-module", + "Adds LLVM lint checks."); + +} // namespace llvm + +// This anchor is used to force the linker to link in the generated object file +// and thus register the LLVMModule. +volatile int LLVMModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/TwineLocalCheck.cpp b/clang-tidy/llvm/TwineLocalCheck.cpp new file mode 100644 index 000000000..3fb5f5d9f --- /dev/null +++ b/clang-tidy/llvm/TwineLocalCheck.cpp @@ -0,0 +1,63 @@ +//===--- 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)) + C = cast(C)->getArg(0)->IgnoreParenImpCasts(); + + SourceRange TypeRange = + VD->getTypeSourceInfo()->getTypeLoc().getSourceRange(); + + // A real Twine, turn it into a std::string. + if (VD->getType()->getCanonicalTypeUnqualified() == + C->getType()->getCanonicalTypeUnqualified()) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + VD->getInit()->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + Diag << FixItHint::CreateReplacement(TypeRange, "std::string") + << FixItHint::CreateInsertion(VD->getInit()->getLocStart(), "(") + << FixItHint::CreateInsertion(EndLoc, ").str()"); + } else { + // Just an implicit conversion. Insert the real type. + Diag << FixItHint::CreateReplacement( + TypeRange, + C->getType().getAsString(Result.Context->getPrintingPolicy())); + } + } +} + +} // namespace llvm +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/llvm/TwineLocalCheck.h b/clang-tidy/llvm/TwineLocalCheck.h new file mode 100644 index 000000000..9f7979b4e --- /dev/null +++ b/clang-tidy/llvm/TwineLocalCheck.h @@ -0,0 +1,33 @@ +//===--- TwineLocalCheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace llvm { + +/// Looks for local `Twine` variables which are prone to use after frees and +/// should be generally avoided. +class TwineLocalCheck : public ClangTidyCheck { +public: + TwineLocalCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace llvm +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_TWINE_LOCAL_CHECK_H diff --git a/clang-tidy/misc/ArgumentCommentCheck.cpp b/clang-tidy/misc/ArgumentCommentCheck.cpp new file mode 100644 index 000000000..f80b9850a --- /dev/null +++ b/clang-tidy/misc/ArgumentCommentCheck.cpp @@ -0,0 +1,199 @@ +//===--- 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" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StrictMode(Options.getLocalOrGlobal("StrictMode", 0) != 0), + IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {} + +void ArgumentCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StrictMode", StrictMode); +} + +void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(unless(cxxOperatorCallExpr()), + // NewCallback's arguments relate to the pointed function, don't + // check them against NewCallback's parameter names. + // FIXME: Make this configurable. + unless(hasDeclaration(functionDecl( + hasAnyName("NewCallback", "NewPermanentCallback"))))) + .bind("expr"), + this); + Finder->addMatcher(cxxConstructExpr().bind("expr"), this); +} + +static std::vector> +getCommentsInRange(ASTContext *Ctx, CharSourceRange Range) { + std::vector> Comments; + auto &SM = Ctx->getSourceManager(); + std::pair BeginLoc = SM.getDecomposedLoc(Range.getBegin()), + EndLoc = SM.getDecomposedLoc(Range.getEnd()); + + if (BeginLoc.first != EndLoc.first) + return Comments; + + bool Invalid = false; + StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid); + if (Invalid) + return Comments; + + const char *StrData = Buffer.data() + BeginLoc.second; + + Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(), + Buffer.begin(), StrData, Buffer.end()); + TheLexer.SetCommentRetentionState(true); + + while (true) { + Token Tok; + if (TheLexer.LexFromRawLexer(Tok)) + break; + if (Tok.getLocation() == Range.getEnd() || Tok.getKind() == tok::eof) + break; + + if (Tok.getKind() == 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())); + } + } + + return Comments; +} + +bool ArgumentCommentCheck::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; +} + +void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx, + const FunctionDecl *Callee, + SourceLocation ArgBeginLoc, + llvm::ArrayRef Args) { + Callee = Callee->getFirstDecl(); + for (unsigned I = 0, + E = std::min(Args.size(), Callee->getNumParams()); + I != E; ++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 = CharSourceRange::getCharRange( + I == 0 ? ArgBeginLoc : Args[I - 1]->getLocEnd(), + Args[I]->getLocStart()); + BeforeArgument = Lexer::makeFileCharRange( + BeforeArgument, Ctx->getSourceManager(), Ctx->getLangOpts()); + + for (auto Comment : getCommentsInRange(Ctx, BeforeArgument)) { + llvm::SmallVector Matches; + if (IdentRE.match(Comment.second, &Matches)) { + if (!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; + } + } + } + } +} + +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); + checkCallArgs( + Result.Context, Construct->getConstructor(), + Construct->getParenOrBraceRange().getBegin(), + llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs())); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ArgumentCommentCheck.h b/clang-tidy/misc/ArgumentCommentCheck.h new file mode 100644 index 000000000..929675b5a --- /dev/null +++ b/clang-tidy/misc/ArgumentCommentCheck.h @@ -0,0 +1,57 @@ +//===--- ArgumentCommentCheck.h - clang-tidy --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks that argument comments match parameter names. +/// +/// The check understands argument comments in the form `/*parameter_name=*/` +/// that are placed right before the argument. +/// +/// \code +/// void f(bool foo); +/// +/// ... +/// f(/*bar=*/true); +/// // warning: argument name 'bar' in comment does not match parameter name 'foo' +/// \endcode +/// +/// The check tries to detect typos and suggest automated fixes for them. +class ArgumentCommentCheck : public ClangTidyCheck { +public: + ArgumentCommentCheck(StringRef Name, ClangTidyContext *Context); + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool StrictMode; + llvm::Regex IdentRE; + + bool isLikelyTypo(llvm::ArrayRef Params, StringRef ArgName, + unsigned ArgIndex); + void checkCallArgs(ASTContext *Ctx, const FunctionDecl *Callee, + SourceLocation ArgBeginLoc, + llvm::ArrayRef Args); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ARGUMENTCOMMENTCHECK_H diff --git a/clang-tidy/misc/AssertSideEffectCheck.cpp b/clang-tidy/misc/AssertSideEffectCheck.cpp new file mode 100644 index 000000000..0b63c0d33 --- /dev/null +++ b/clang-tidy/misc/AssertSideEffectCheck.cpp @@ -0,0 +1,127 @@ +//===--- AssertSideEffectCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AssertSideEffectCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER_P(Expr, hasSideEffect, bool, CheckFunctionCalls) { + const Expr *E = &Node; + + if (const auto *Op = dyn_cast(E)) { + UnaryOperator::Opcode OC = Op->getOpcode(); + return OC == UO_PostInc || OC == UO_PostDec || OC == UO_PreInc || + OC == UO_PreDec; + } + + if (const auto *Op = dyn_cast(E)) { + return Op->isAssignmentOp(); + } + + if (const auto *OpCallExpr = dyn_cast(E)) { + OverloadedOperatorKind OpKind = OpCallExpr->getOperator(); + return OpKind == OO_Equal || OpKind == OO_PlusEqual || + OpKind == OO_MinusEqual || OpKind == OO_StarEqual || + OpKind == OO_SlashEqual || OpKind == OO_AmpEqual || + OpKind == OO_PipeEqual || OpKind == OO_CaretEqual || + OpKind == OO_LessLessEqual || OpKind == OO_GreaterGreaterEqual || + OpKind == OO_PlusPlus || OpKind == OO_MinusMinus || + OpKind == OO_PercentEqual || OpKind == OO_New || + OpKind == OO_Delete || OpKind == OO_Array_New || + OpKind == OO_Array_Delete; + } + + if (const auto *CExpr = dyn_cast(E)) { + bool Result = CheckFunctionCalls; + if (const auto *FuncDecl = CExpr->getDirectCallee()) { + if (FuncDecl->getDeclName().isIdentifier() && + FuncDecl->getName() == "__builtin_expect") // exceptions come here + Result = false; + else if (const auto *MethodDecl = dyn_cast(FuncDecl)) + Result &= !MethodDecl->isConst(); + } + return Result; + } + + return isa(E) || isa(E) || isa(E); +} + +} // namespace + +AssertSideEffectCheck::AssertSideEffectCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckFunctionCalls(Options.get("CheckFunctionCalls", false)), + RawAssertList(Options.get("AssertMacros", "assert")) { + StringRef(RawAssertList).split(AssertMacros, ",", -1, false); +} + +// The options are explained in AssertSideEffectCheck.h. +void AssertSideEffectCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckFunctionCalls", CheckFunctionCalls); + Options.store(Opts, "AssertMacros", RawAssertList); +} + +void AssertSideEffectCheck::registerMatchers(MatchFinder *Finder) { + auto DescendantWithSideEffect = + hasDescendant(expr(hasSideEffect(CheckFunctionCalls))); + auto ConditionWithSideEffect = hasCondition(DescendantWithSideEffect); + Finder->addMatcher( + stmt( + anyOf(conditionalOperator(ConditionWithSideEffect), + ifStmt(ConditionWithSideEffect), + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(unaryOperator( + hasOperatorName("!"), + hasUnaryOperand(DescendantWithSideEffect)))))) + .bind("condStmt"), + this); +} + +void AssertSideEffectCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const LangOptions LangOpts = getLangOpts(); + SourceLocation Loc = Result.Nodes.getNodeAs("condStmt")->getLocStart(); + + StringRef AssertMacroName; + while (Loc.isValid() && Loc.isMacroID()) { + StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LangOpts); + + // Check if this macro is an assert. + if (std::find(AssertMacros.begin(), AssertMacros.end(), MacroName) != + AssertMacros.end()) { + AssertMacroName = MacroName; + break; + } + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + if (AssertMacroName.empty()) + return; + + diag(Loc, "found %0() with side effect") << AssertMacroName; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/AssertSideEffectCheck.h b/clang-tidy/misc/AssertSideEffectCheck.h new file mode 100644 index 000000000..2bb25e2d4 --- /dev/null +++ b/clang-tidy/misc/AssertSideEffectCheck.h @@ -0,0 +1,52 @@ +//===--- AssertSideEffectCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds `assert()` with side effect. +/// +/// The condition of `assert()` is evaluated only in debug builds so a +/// condition with side effect can cause different behavior in debug / release +/// builds. +/// +/// There are two options: +/// +/// - `AssertMacros`: A comma-separated list of the names of assert macros to +/// be checked. +/// - `CheckFunctionCalls`: Whether to treat non-const member and non-member +/// functions as they produce side effects. Disabled by default because it +/// can increase the number of false positive warnings. +class AssertSideEffectCheck : public ClangTidyCheck { +public: + AssertSideEffectCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool CheckFunctionCalls; + const std::string RawAssertList; + SmallVector AssertMacros; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSERTSIDEEFFECTCHECK_H diff --git a/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp b/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp new file mode 100644 index 000000000..f83112583 --- /dev/null +++ b/clang-tidy/misc/BoolPointerImplicitConversionCheck.cpp @@ -0,0 +1,73 @@ +//===--- BoolPointerImplicitConversionCheck.cpp - clang-tidy --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BoolPointerImplicitConversionCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void BoolPointerImplicitConversionCheck::registerMatchers(MatchFinder *Finder) { + // Look for ifs that have an implicit bool* to bool conversion in the + // condition. Filter negations. + Finder->addMatcher( + ifStmt(hasCondition(findAll(implicitCastExpr( + allOf(unless(hasParent(unaryOperator(hasOperatorName("!")))), + hasSourceExpression(expr( + hasType(pointerType(pointee(booleanType()))), + ignoringParenImpCasts(declRefExpr().bind("expr")))), + hasCastKind(CK_PointerToBoolean))))), + unless(isInTemplateInstantiation())) + .bind("if"), + this); +} + +void BoolPointerImplicitConversionCheck::check( + const MatchFinder::MatchResult &Result) { + auto *If = Result.Nodes.getNodeAs("if"); + auto *Var = Result.Nodes.getNodeAs("expr"); + + // Ignore macros. + if (Var->getLocStart().isMacroID()) + return; + + // Only allow variable accesses for now, no function calls or member exprs. + // Check that we don't dereference the variable anywhere within the if. This + // avoids false positives for checks of the pointer for nullptr before it is + // dereferenced. If there is a dereferencing operator on this variable don't + // emit a diagnostic. Also ignore array subscripts. + const Decl *D = Var->getDecl(); + auto DeclRef = ignoringParenImpCasts(declRefExpr(to(equalsNode(D)))); + if (!match(findAll( + unaryOperator(hasOperatorName("*"), hasUnaryOperand(DeclRef))), + *If, *Result.Context) + .empty() || + !match(findAll(arraySubscriptExpr(hasBase(DeclRef))), *If, + *Result.Context) + .empty() || + // FIXME: We should still warn if the paremater is implicitly converted to + // bool. + !match(findAll(callExpr(hasAnyArgument(ignoringParenImpCasts(DeclRef)))), + *If, *Result.Context) + .empty() || + !match(findAll(cxxDeleteExpr(has(ignoringParenImpCasts(expr(DeclRef))))), + *If, *Result.Context) + .empty()) + return; + + diag(Var->getLocStart(), "dubious check of 'bool *' against 'nullptr', did " + "you mean to dereference it?") + << FixItHint::CreateInsertion(Var->getLocStart(), "*"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/BoolPointerImplicitConversionCheck.h b/clang-tidy/misc/BoolPointerImplicitConversionCheck.h new file mode 100644 index 000000000..d8a90f357 --- /dev/null +++ b/clang-tidy/misc/BoolPointerImplicitConversionCheck.h @@ -0,0 +1,42 @@ +//===--- BoolPointerImplicitConversionCheck.h - clang-tidy ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for conditions based on implicit conversion from a bool pointer to +/// bool. +/// +/// Example: +/// +/// \code +/// bool *p; +/// if (p) { +/// // Never used in a pointer-specific way. +/// } +/// \endcode +class BoolPointerImplicitConversionCheck : public ClangTidyCheck { +public: + BoolPointerImplicitConversionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_BOOLPOINTERIMPLICITCONVERSIONCHECK_H diff --git a/clang-tidy/misc/CMakeLists.txt b/clang-tidy/misc/CMakeLists.txt new file mode 100644 index 000000000..dd01f52b7 --- /dev/null +++ b/clang-tidy/misc/CMakeLists.txt @@ -0,0 +1,59 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyMiscModule + ArgumentCommentCheck.cpp + AssertSideEffectCheck.cpp + MisplacedConstCheck.cpp + UnconventionalAssignOperatorCheck.cpp + BoolPointerImplicitConversionCheck.cpp + DanglingHandleCheck.cpp + DefinitionsInHeadersCheck.cpp + FoldInitTypeCheck.cpp + ForwardDeclarationNamespaceCheck.cpp + InaccurateEraseCheck.cpp + IncorrectRoundings.cpp + InefficientAlgorithmCheck.cpp + MacroParenthesesCheck.cpp + MacroRepeatedSideEffectsCheck.cpp + MiscTidyModule.cpp + MisplacedWideningCastCheck.cpp + MoveConstantArgumentCheck.cpp + MoveConstructorInitCheck.cpp + MoveForwardingReferenceCheck.cpp + MultipleStatementMacroCheck.cpp + NewDeleteOverloadsCheck.cpp + NoexceptMoveConstructorCheck.cpp + NonCopyableObjects.cpp + RedundantExpressionCheck.cpp + SizeofContainerCheck.cpp + SizeofExpressionCheck.cpp + StaticAssertCheck.cpp + StringCompareCheck.cpp + StringConstructorCheck.cpp + StringIntegerAssignmentCheck.cpp + StringLiteralWithEmbeddedNulCheck.cpp + SuspiciousEnumUsageCheck.cpp + SuspiciousMissingCommaCheck.cpp + SuspiciousSemicolonCheck.cpp + SuspiciousStringCompareCheck.cpp + SwappedArgumentsCheck.cpp + ThrowByValueCatchByReferenceCheck.cpp + UndelegatedConstructor.cpp + UniqueptrResetReleaseCheck.cpp + UnusedAliasDeclsCheck.cpp + UnusedParametersCheck.cpp + UnusedRAIICheck.cpp + UnusedUsingDeclsCheck.cpp + UseAfterMoveCheck.cpp + VirtualNearMissCheck.cpp + + LINK_LIBS + clangAnalysis + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/misc/DanglingHandleCheck.cpp b/clang-tidy/misc/DanglingHandleCheck.cpp new file mode 100644 index 000000000..67f83d27b --- /dev/null +++ b/clang-tidy/misc/DanglingHandleCheck.cpp @@ -0,0 +1,177 @@ +//===--- DanglingHandleCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DanglingHandleCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +ast_matchers::internal::BindableMatcher +handleFrom(const ast_matchers::internal::Matcher &IsAHandle, + const ast_matchers::internal::Matcher &Arg) { + return cxxConstructExpr(hasDeclaration(cxxMethodDecl(ofClass(IsAHandle))), + hasArgument(0, Arg)); +} + +ast_matchers::internal::Matcher handleFromTemporaryValue( + const ast_matchers::internal::Matcher &IsAHandle) { + // If a ternary operator returns a temporary value, then both branches hold a + // temporary value. If one of them is not a temporary then it must be copied + // into one to satisfy the type of the operator. + const auto TemporaryTernary = + conditionalOperator(hasTrueExpression(cxxBindTemporaryExpr()), + hasFalseExpression(cxxBindTemporaryExpr())); + + return handleFrom(IsAHandle, anyOf(cxxBindTemporaryExpr(), TemporaryTernary)); +} + +ast_matchers::internal::Matcher isASequence() { + return hasAnyName("::std::deque", "::std::forward_list", "::std::list", + "::std::vector"); +} + +ast_matchers::internal::Matcher isASet() { + return hasAnyName("::std::set", "::std::multiset", "::std::unordered_set", + "::std::unordered_multiset"); +} + +ast_matchers::internal::Matcher isAMap() { + return hasAnyName("::std::map", "::std::multimap", "::std::unordered_map", + "::std::unordered_multimap"); +} + +ast_matchers::internal::BindableMatcher makeContainerMatcher( + const ast_matchers::internal::Matcher &IsAHandle) { + // This matcher could be expanded to detect: + // - Constructors: eg. vector(3, string("A")); + // - emplace*(): This requires a different logic to determine that + // the conversion will happen inside the container. + // - map's insert: This requires detecting that the pair conversion triggers + // the bug. A little more complicated than what we have now. + return callExpr( + hasAnyArgument( + ignoringParenImpCasts(handleFromTemporaryValue(IsAHandle))), + anyOf( + // For sequences: assign, push_back, resize. + cxxMemberCallExpr( + callee(functionDecl(hasAnyName("assign", "push_back", "resize"))), + on(expr(hasType(recordDecl(isASequence()))))), + // For sequences and sets: insert. + cxxMemberCallExpr( + callee(functionDecl(hasName("insert"))), + on(expr(hasType(recordDecl(anyOf(isASequence(), isASet())))))), + // For maps: operator[]. + cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(isAMap()))), + hasOverloadedOperatorName("[]")))); +} + +} // anonymous namespace + +DanglingHandleCheck::DanglingHandleCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + HandleClasses(utils::options::parseStringList(Options.get( + "HandleClasses", + "std::basic_string_view;std::experimental::basic_string_view"))), + IsAHandle(cxxRecordDecl(hasAnyName(std::vector( + HandleClasses.begin(), HandleClasses.end()))) + .bind("handle")) {} + +void DanglingHandleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "HandleClasses", + utils::options::serializeStringList(HandleClasses)); +} + +void DanglingHandleCheck::registerMatchersForVariables(MatchFinder *Finder) { + const auto ConvertedHandle = handleFromTemporaryValue(IsAHandle); + + // Find 'Handle foo(ReturnsAValue());' + Finder->addMatcher( + varDecl(hasType(cxxRecordDecl(IsAHandle)), + hasInitializer( + exprWithCleanups(has(ignoringParenImpCasts(ConvertedHandle))) + .bind("bad_stmt"))), + this); + + // Find 'Handle foo = ReturnsAValue();' + Finder->addMatcher( + varDecl( + hasType(cxxRecordDecl(IsAHandle)), unless(parmVarDecl()), + hasInitializer(exprWithCleanups(has(ignoringParenImpCasts(handleFrom( + IsAHandle, ConvertedHandle)))) + .bind("bad_stmt"))), + this); + // Find 'foo = ReturnsAValue(); // foo is Handle' + Finder->addMatcher( + cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(IsAHandle))), + hasOverloadedOperatorName("="), + hasArgument(1, ConvertedHandle)) + .bind("bad_stmt"), + this); + + // Container insertions that will dangle. + Finder->addMatcher(makeContainerMatcher(IsAHandle).bind("bad_stmt"), this); +} + +void DanglingHandleCheck::registerMatchersForReturn(MatchFinder *Finder) { + // Return a local. + Finder->addMatcher( + returnStmt( + // The AST contains two constructor calls: + // 1. Value to Handle conversion. + // 2. Handle copy construction. + // We have to match both. + has(ignoringImplicit(handleFrom( + IsAHandle, + handleFrom(IsAHandle, declRefExpr(to(varDecl( + // Is function scope ... + hasAutomaticStorageDuration(), + // ... and it is a local array or Value. + anyOf(hasType(arrayType()), + hasType(recordDecl( + unless(IsAHandle))))))))))), + // Temporary fix for false positives inside lambdas. + unless(hasAncestor(lambdaExpr()))) + .bind("bad_stmt"), + this); + + // Return a temporary. + Finder->addMatcher( + returnStmt( + has(ignoringParenImpCasts(exprWithCleanups(has(ignoringParenImpCasts( + handleFrom(IsAHandle, handleFromTemporaryValue(IsAHandle)))))))) + .bind("bad_stmt"), + this); +} + +void DanglingHandleCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersForVariables(Finder); + registerMatchersForReturn(Finder); +} + +void DanglingHandleCheck::check(const MatchFinder::MatchResult &Result) { + auto *Handle = Result.Nodes.getNodeAs("handle"); + diag(Result.Nodes.getNodeAs("bad_stmt")->getLocStart(), + "%0 outlives its value") + << Handle->getQualifiedNameAsString(); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/DanglingHandleCheck.h b/clang-tidy/misc/DanglingHandleCheck.h new file mode 100644 index 000000000..48797a41d --- /dev/null +++ b/clang-tidy/misc/DanglingHandleCheck.h @@ -0,0 +1,43 @@ +//===--- DanglingHandleCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DANGLING_HANDLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DANGLING_HANDLE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect dangling references in value handlers like +/// std::experimental::string_view. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-dangling-handle.html +class DanglingHandleCheck : public ClangTidyCheck { +public: + DanglingHandleCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void registerMatchersForVariables(ast_matchers::MatchFinder *Finder); + void registerMatchersForReturn(ast_matchers::MatchFinder *Finder); + + const std::vector HandleClasses; + const ast_matchers::internal::Matcher IsAHandle; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DANGLING_HANDLE_H diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.cpp b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp new file mode 100644 index 000000000..f74e04e5e --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.cpp @@ -0,0 +1,149 @@ +//===--- DefinitionsInHeadersCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DefinitionsInHeadersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER_P(NamedDecl, usesHeaderFileExtension, + utils::HeaderFileExtensionsSet, HeaderFileExtensions) { + return utils::isExpansionLocInHeaderFile( + Node.getLocStart(), Finder->getASTContext().getSourceManager(), + HeaderFileExtensions); +} + +} // namespace + +DefinitionsInHeadersCheck::DefinitionsInHeadersCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + UseHeaderFileExtension(Options.get("UseHeaderFileExtension", true)), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) { + if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ',')) { + // FIXME: Find a more suitable way to handle invalid configuration + // options. + llvm::errs() << "Invalid header file extension: " + << RawStringHeaderFileExtensions << "\n"; + } +} + +void DefinitionsInHeadersCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UseHeaderFileExtension", UseHeaderFileExtension); + Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions); +} + +void DefinitionsInHeadersCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + auto DefinitionMatcher = + anyOf(functionDecl(isDefinition(), unless(isDeleted())), + varDecl(isDefinition())); + if (UseHeaderFileExtension) { + Finder->addMatcher(namedDecl(DefinitionMatcher, + usesHeaderFileExtension(HeaderFileExtensions)) + .bind("name-decl"), + this); + } else { + Finder->addMatcher( + namedDecl(DefinitionMatcher, + anyOf(usesHeaderFileExtension(HeaderFileExtensions), + unless(isExpansionInMainFile()))) + .bind("name-decl"), + this); + } +} + +void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) { + // Don't run the check in failing TUs. + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + // C++ [basic.def.odr] p6: + // There can be more than one definition of a class type, enumeration type, + // inline function with external linkage, class template, non-static function + // template, static data member of a class template, member function of a + // class template, or template specialization for which some template + // parameters are not specifiedin a program provided that each definition + // appears in a different translation unit, and provided the definitions + // satisfy the following requirements. + const auto *ND = Result.Nodes.getNodeAs("name-decl"); + assert(ND); + if (ND->isInvalidDecl()) + return; + + // Internal linkage variable definitions are ignored for now: + // const int a = 1; + // static int b = 1; + // + // Although these might also cause ODR violations, we can be less certain and + // should try to keep the false-positive rate down. + if (ND->getLinkageInternal() == InternalLinkage) + return; + + if (const auto *FD = dyn_cast(ND)) { + // Inline functions are allowed. + if (FD->isInlined()) + return; + // Function templates are allowed. + if (FD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) + return; + // Function template full specialization is prohibited in header file. + if (FD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) + 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(); + } + } + + diag(FD->getLocation(), + "function %0 defined in a header file; " + "function definitions in header files can lead to ODR violations") + << 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; + if (VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation) + return; + // Ignore variable definition within function scope. + if (VD->hasLocalStorage() || VD->isStaticLocal()) + return; + + diag(VD->getLocation(), + "variable %0 defined in a header file; " + "variable definitions in header files can lead to ODR violations") + << VD; + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/DefinitionsInHeadersCheck.h b/clang-tidy/misc/DefinitionsInHeadersCheck.h new file mode 100644 index 000000000..428b05cd5 --- /dev/null +++ b/clang-tidy/misc/DefinitionsInHeadersCheck.h @@ -0,0 +1,51 @@ +//===--- DefinitionsInHeadersCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds non-extern non-inline function and variable definitions in header +/// files, which can lead to potential ODR violations. +/// +/// The check supports these options: +/// - `UseHeaderFileExtension`: Whether to use file extension to distinguish +/// header files. True by default. +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extension should not contain "." prefix). +/// ",h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-definitions-in-headers.html +class DefinitionsInHeadersCheck : public ClangTidyCheck { +public: + DefinitionsInHeadersCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool UseHeaderFileExtension; + const std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEFINITIONS_IN_HEADERS_H diff --git a/clang-tidy/misc/FoldInitTypeCheck.cpp b/clang-tidy/misc/FoldInitTypeCheck.cpp new file mode 100644 index 000000000..c5d27201b --- /dev/null +++ b/clang-tidy/misc/FoldInitTypeCheck.cpp @@ -0,0 +1,140 @@ +//===--- FoldInitTypeCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FoldInitTypeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void FoldInitTypeCheck::registerMatchers(MatchFinder *Finder) { + // We match functions of interest and bind the iterator and init value types. + // Note: Right now we check only builtin types. + const auto BuiltinTypeWithId = [](const char *ID) { + return hasCanonicalType(builtinType().bind(ID)); + }; + const auto IteratorWithValueType = [&BuiltinTypeWithId](const char *ID) { + return anyOf( + // Pointer types. + pointsTo(BuiltinTypeWithId(ID)), + // Iterator types. + recordType(hasDeclaration(has(typedefNameDecl( + hasName("value_type"), hasType(BuiltinTypeWithId(ID))))))); + }; + + const auto IteratorParam = parmVarDecl( + hasType(hasCanonicalType(IteratorWithValueType("IterValueType")))); + const auto Iterator2Param = parmVarDecl( + hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType")))); + const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType"))); + + // std::accumulate, std::reduce. + Finder->addMatcher( + callExpr(callee(functionDecl( + hasAnyName("::std::accumulate", "::std::reduce"), + hasParameter(0, IteratorParam), hasParameter(2, InitParam))), + argumentCountIs(3)) + .bind("Call"), + this); + // std::inner_product. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::std::inner_product"), + hasParameter(0, IteratorParam), + hasParameter(2, Iterator2Param), + hasParameter(3, InitParam))), + argumentCountIs(4)) + .bind("Call"), + this); + // std::reduce with a policy. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::std::reduce"), + hasParameter(1, IteratorParam), + hasParameter(3, InitParam))), + argumentCountIs(4)) + .bind("Call"), + this); + // std::inner_product with a policy. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::std::inner_product"), + hasParameter(1, IteratorParam), + hasParameter(3, Iterator2Param), + hasParameter(4, InitParam))), + argumentCountIs(5)) + .bind("Call"), + this); +} + +/// Returns true if ValueType is allowed to fold into InitType, i.e. if: +/// static_cast(ValueType{some_value}) +/// does not result in trucation. +static bool isValidBuiltinFold(const BuiltinType &ValueType, + const BuiltinType &InitType, + const ASTContext &Context) { + const auto ValueTypeSize = Context.getTypeSize(&ValueType); + const auto InitTypeSize = Context.getTypeSize(&InitType); + // It's OK to fold a float into a float of bigger or equal size, but not OK to + // fold into an int. + if (ValueType.isFloatingPoint()) + return InitType.isFloatingPoint() && InitTypeSize >= ValueTypeSize; + // It's OK to fold an int into: + // - an int of the same size and signedness. + // - a bigger int, regardless of signedness. + // - FIXME: should it be a warning to fold into floating point? + if (ValueType.isInteger()) { + if (InitType.isInteger()) { + if (InitType.isSignedInteger() == ValueType.isSignedInteger()) + return InitTypeSize >= ValueTypeSize; + return InitTypeSize > ValueTypeSize; + } + if (InitType.isFloatingPoint()) + return InitTypeSize >= ValueTypeSize; + } + return false; +} + +/// Prints a diagnostic if IterValueType doe snot fold into IterValueType (see +// isValidBuiltinFold for details). +void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType, + const BuiltinType &InitType, + const ASTContext &Context, + const CallExpr &CallNode) { + if (!isValidBuiltinFold(IterValueType, InitType, Context)) { + diag(CallNode.getExprLoc(), "folding type %0 into type %1 might result in " + "loss of precision") + << IterValueType.desugar() << InitType.desugar(); + } +} + +void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) { + // Given the iterator and init value type retreived by the matchers, + // we check that the ::value_type of the iterator is compatible with + // the init value type. + const auto *InitType = Result.Nodes.getNodeAs("InitType"); + const auto *IterValueType = + Result.Nodes.getNodeAs("IterValueType"); + assert(InitType != nullptr); + assert(IterValueType != nullptr); + + const auto *CallNode = Result.Nodes.getNodeAs("Call"); + assert(CallNode != nullptr); + + doCheck(*IterValueType, *InitType, *Result.Context, *CallNode); + + if (const auto *Iter2ValueType = + Result.Nodes.getNodeAs("Iter2ValueType")) + doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/FoldInitTypeCheck.h b/clang-tidy/misc/FoldInitTypeCheck.h new file mode 100644 index 000000000..df4ec88fa --- /dev/null +++ b/clang-tidy/misc/FoldInitTypeCheck.h @@ -0,0 +1,44 @@ +//===--- FoldInitTypeCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find and flag invalid initializer values in folds, e.g. std::accumulate. +/// Example: +/// \code +/// auto v = {65536L * 65536 * 65536}; +/// std::accumulate(begin(v), end(v), 0 /* int type is too small */); +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-fold-init-type.html +class FoldInitTypeCheck : public ClangTidyCheck { +public: + FoldInitTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void doCheck(const BuiltinType &IterValueType, const BuiltinType &InitType, + const ASTContext &Context, const CallExpr &CallNode); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H diff --git a/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp new file mode 100644 index 000000000..1487e8f18 --- /dev/null +++ b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.cpp @@ -0,0 +1,174 @@ +//===--- ForwardDeclarationNamespaceCheck.cpp - clang-tidy ------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForwardDeclarationNamespaceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void ForwardDeclarationNamespaceCheck::registerMatchers(MatchFinder *Finder) { + // Match all class declarations/definitions *EXCEPT* + // 1. implicit classes, e.g. `class A {};` has implicit `class A` inside `A`. + // 2. nested classes declared/defined inside another class. + // 3. template class declaration, template instantiation or + // specialization (NOTE: extern specialization is filtered out by + // `unless(hasAncestor(cxxRecordDecl()))`). + auto IsInSpecialization = hasAncestor( + decl(anyOf(cxxRecordDecl(isExplicitTemplateSpecialization()), + functionDecl(isExplicitTemplateSpecialization())))); + Finder->addMatcher( + cxxRecordDecl( + hasParent(decl(anyOf(namespaceDecl(), translationUnitDecl()))), + unless(isImplicit()), unless(hasAncestor(cxxRecordDecl())), + unless(isInstantiated()), unless(IsInSpecialization), + unless(classTemplateSpecializationDecl())) + .bind("record_decl"), + this); + + // Match all friend declarations. Classes used in friend declarations are not + // marked as referenced in AST. We need to record all record classes used in + // friend declarations. + Finder->addMatcher(friendDecl().bind("friend_decl"), this); +} + +void ForwardDeclarationNamespaceCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *RecordDecl = + Result.Nodes.getNodeAs("record_decl")) { + StringRef DeclName = RecordDecl->getName(); + if (RecordDecl->isThisDeclarationADefinition()) { + DeclNameToDefinitions[DeclName].push_back(RecordDecl); + } else { + // If a declaration has no definition, the definition could be in another + // namespace (a wrong namespace). + // NOTE: even a declaration does have definition, we still need it to + // compare with other declarations. + DeclNameToDeclarations[DeclName].push_back(RecordDecl); + } + } else { + const auto *Decl = Result.Nodes.getNodeAs("friend_decl"); + assert(Decl && "Decl is neither record_decl nor friend decl!"); + + // Classes used in friend delarations are not marked referenced in AST, + // so we need to check classes used in friend declarations manually to + // reduce the rate of false positive. + // For example, in + // \code + // struct A; + // struct B { friend A; }; + // \endcode + // `A` will not be marked as "referenced" in the AST. + if (const TypeSourceInfo *Tsi = Decl->getFriendType()) { + QualType Desugared = Tsi->getType().getDesugaredType(*Result.Context); + FriendTypes.insert(Desugared.getTypePtr()); + } + } +} + +static bool haveSameNamespaceOrTranslationUnit(const CXXRecordDecl *Decl1, + const CXXRecordDecl *Decl2) { + const DeclContext *ParentDecl1 = Decl1->getLexicalParent(); + const DeclContext *ParentDecl2 = Decl2->getLexicalParent(); + + // Since we only matched declarations whose parent is Namespace or + // TranslationUnit declaration, the parent should be either a translation unit + // or namespace. + if (ParentDecl1->getDeclKind() == Decl::TranslationUnit || + ParentDecl2->getDeclKind() == Decl::TranslationUnit) { + return ParentDecl1 == ParentDecl2; + } + assert(ParentDecl1->getDeclKind() == Decl::Namespace && + "ParentDecl1 declaration must be a namespace"); + assert(ParentDecl2->getDeclKind() == Decl::Namespace && + "ParentDecl2 declaration must be a namespace"); + auto *Ns1 = NamespaceDecl::castFromDeclContext(ParentDecl1); + auto *Ns2 = NamespaceDecl::castFromDeclContext(ParentDecl2); + return Ns1->getOriginalNamespace() == Ns2->getOriginalNamespace(); +} + +static std::string getNameOfNamespace(const CXXRecordDecl *Decl) { + const auto *ParentDecl = Decl->getLexicalParent(); + if (ParentDecl->getDeclKind() == Decl::TranslationUnit) { + return "(global)"; + } + const auto *NsDecl = cast(ParentDecl); + std::string Ns; + llvm::raw_string_ostream OStream(Ns); + NsDecl->printQualifiedName(OStream); + OStream.flush(); + return Ns.empty() ? "(global)" : Ns; +} + +void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() { + // Iterate each group of declarations by name. + for (const auto &KeyValuePair : DeclNameToDeclarations) { + const auto &Declarations = KeyValuePair.second; + // If more than 1 declaration exists, we check if all are in the same + // namespace. + for (const auto *CurDecl : Declarations) { + if (CurDecl->hasDefinition() || CurDecl->isReferenced()) { + continue; // Skip forward declarations that are used/referenced. + } + if (FriendTypes.count(CurDecl->getTypeForDecl()) != 0) { + continue; // Skip forward declarations referenced as friend. + } + if (CurDecl->getLocation().isMacroID() || + CurDecl->getLocation().isInvalid()) { + continue; + } + // Compare with all other declarations with the same name. + for (const auto *Decl : Declarations) { + if (Decl == CurDecl) { + continue; // Don't compare with self. + } + if (!CurDecl->hasDefinition() && + !haveSameNamespaceOrTranslationUnit(CurDecl, Decl)) { + diag(CurDecl->getLocation(), + "declaration %0 is never referenced, but a declaration with " + "the same name found in another namespace '%1'") + << CurDecl << getNameOfNamespace(Decl); + diag(Decl->getLocation(), "a declaration of %0 is found here", + DiagnosticIDs::Note) + << Decl; + break; // FIXME: We only generate one warning for each declaration. + } + } + // Check if a definition in another namespace exists. + const auto DeclName = CurDecl->getName(); + if (DeclNameToDefinitions.find(DeclName) == DeclNameToDefinitions.end()) { + continue; // No definition in this translation unit, we can skip it. + } + // Make a warning for each definition with the same name (in other + // namespaces). + const auto &Definitions = DeclNameToDefinitions[DeclName]; + for (const auto *Def : Definitions) { + diag(CurDecl->getLocation(), + "no definition found for %0, but a definition with " + "the same name %1 found in another namespace '%2'") + << CurDecl << Def << getNameOfNamespace(Def); + diag(Def->getLocation(), "a definition of %0 is found here", + DiagnosticIDs::Note) + << Def; + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h new file mode 100644 index 000000000..dd7042d36 --- /dev/null +++ b/clang-tidy/misc/ForwardDeclarationNamespaceCheck.h @@ -0,0 +1,59 @@ +//===--- ForwardDeclarationNamespaceCheck.h - clang-tidy --------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallPtrSet.h" +#include +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks if an unused forward declaration is in a wrong namespace. +/// +/// The check inspects all unused forward declarations and checks if there is +/// any declaration/definition with the same name, which could indicate +/// that the forward declaration is potentially in a wrong namespace. +/// +/// \code +/// namespace na { struct A; } +/// namespace nb { struct A {} }; +/// nb::A a; +/// // warning : no definition found for 'A', but a definition with the same +/// name 'A' found in another namespace 'nb::' +/// \endcode +/// +/// This check can only generate warnings, but it can't suggest fixes at this +/// point. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-forward-declaration-namespace.html +class ForwardDeclarationNamespaceCheck : public ClangTidyCheck { +public: + ForwardDeclarationNamespaceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + llvm::StringMap> DeclNameToDefinitions; + llvm::StringMap> DeclNameToDeclarations; + llvm::SmallPtrSet FriendTypes; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FORWARDDECLARATIONNAMESPACECHECK_H diff --git a/clang-tidy/misc/InaccurateEraseCheck.cpp b/clang-tidy/misc/InaccurateEraseCheck.cpp new file mode 100644 index 000000000..8b0f8fd00 --- /dev/null +++ b/clang-tidy/misc/InaccurateEraseCheck.cpp @@ -0,0 +1,73 @@ +//===--- InaccurateEraseCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InaccurateEraseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +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 CheckForEndCall = hasArgument( + 1, anyOf(cxxConstructExpr(has(ignoringParenImpCasts( + cxxMemberCallExpr(callee(cxxMethodDecl(hasName("end")))) + .bind("InaccEndCall")))), + anything())); + + Finder->addMatcher( + cxxMemberCallExpr( + on(hasType(namedDecl(matchesName("^::std::")))), + callee(cxxMethodDecl(hasName("erase"))), argumentCountIs(1), + hasArgument(0, has(ignoringParenImpCasts( + callExpr(callee(functionDecl(matchesName( + "^::std::(remove(_if)?|unique)$"))), + CheckForEndCall) + .bind("InaccAlgCall")))), + unless(isInTemplateInstantiation())) + .bind("InaccErase"), + this); +} + +void InaccurateEraseCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("InaccErase"); + const auto *EndExpr = + Result.Nodes.getNodeAs("InaccEndCall"); + const SourceLocation Loc = MemberCall->getLocStart(); + + FixItHint Hint; + + if (!Loc.isMacroID() && EndExpr) { + const auto *AlgCall = Result.Nodes.getNodeAs("InaccAlgCall"); + std::string ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(EndExpr->getSourceRange()), + *Result.SourceManager, getLangOpts()); + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + AlgCall->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + Hint = FixItHint::CreateInsertion(EndLoc, ", " + ReplacementText); + } + + diag(Loc, "this call will remove at most one item even when multiple items " + "should be removed") + << Hint; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/InaccurateEraseCheck.h b/clang-tidy/misc/InaccurateEraseCheck.h new file mode 100644 index 000000000..623e1c236 --- /dev/null +++ b/clang-tidy/misc/InaccurateEraseCheck.h @@ -0,0 +1,38 @@ +//===--- InaccurateEraseCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for inaccurate use of the `erase()` method. +/// +/// Algorithms like `remove()` do not actually remove any element from the +/// container but return an iterator to the first redundant element at the end +/// of the container. These redundant elements must be removed using the +/// `erase()` method. This check warns when not all of the elements will be +/// removed due to using an inappropriate overload. +class InaccurateEraseCheck : public ClangTidyCheck { +public: + InaccurateEraseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INACCURATEERASECHECK_H diff --git a/clang-tidy/misc/IncorrectRoundings.cpp b/clang-tidy/misc/IncorrectRoundings.cpp new file mode 100644 index 000000000..7f9b90b83 --- /dev/null +++ b/clang-tidy/misc/IncorrectRoundings.cpp @@ -0,0 +1,71 @@ +//===--- IncorrectRoundings.cpp - clang-tidy ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncorrectRoundings.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER(FloatingLiteral, floatHalf) { + const auto &literal = Node.getValue(); + if ((&Node.getSemantics()) == &llvm::APFloat::IEEEsingle()) + return literal.convertToFloat() == 0.5f; + if ((&Node.getSemantics()) == &llvm::APFloat::IEEEdouble()) + return literal.convertToDouble() == 0.5; + return false; +} +} // namespace + +void IncorrectRoundings::registerMatchers(MatchFinder *MatchFinder) { + // Match a floating literal with value 0.5. + auto FloatHalf = floatLiteral(floatHalf()); + + // Match a floating point expression. + auto FloatType = expr(hasType(realFloatingPointType())); + + // Match a floating literal of 0.5 or a floating literal of 0.5 implicitly. + // cast to floating type. + auto FloatOrCastHalf = + anyOf(FloatHalf, + implicitCastExpr(FloatType, has(ignoringParenImpCasts(FloatHalf)))); + + // Match if either the LHS or RHS is a floating literal of 0.5 or a floating + // literal of 0.5 and the other is of type double or vice versa. + auto OneSideHalf = anyOf(allOf(hasLHS(FloatOrCastHalf), hasRHS(FloatType)), + allOf(hasRHS(FloatOrCastHalf), hasLHS(FloatType))); + + // Find expressions of cast to int of the sum of a floating point expression + // and 0.5. + MatchFinder->addMatcher( + implicitCastExpr( + hasImplicitDestinationType(isInteger()), + ignoringParenCasts(binaryOperator(hasOperatorName("+"), OneSideHalf))) + .bind("CastExpr"), + this); +} + +void IncorrectRoundings::check(const MatchFinder::MatchResult &Result) { + const auto *CastExpr = Result.Nodes.getNodeAs("CastExpr"); + diag(CastExpr->getLocStart(), + "casting (double + 0.5) to integer leads to incorrect rounding; " + "consider using lround (#include ) instead"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/IncorrectRoundings.h b/clang-tidy/misc/IncorrectRoundings.h new file mode 100644 index 000000000..7b5f6a0a2 --- /dev/null +++ b/clang-tidy/misc/IncorrectRoundings.h @@ -0,0 +1,39 @@ +//===--- IncorrectRoundings.h - clang-tidy ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCORRECTROUNDINGS_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCORRECTROUNDINGS_H_ + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// \brief Checks the usage of patterns known to produce incorrect rounding. +/// Programmers often use +/// (int)(double_expression + 0.5) +/// to round the double expression to an integer. The problem with this +/// 1. It is unnecessarily slow. +/// 2. It is incorrect. The number 0.499999975 (smallest representable float +/// number below 0.5) rounds to 1.0. Even worse behavior for negative +/// numbers where both -0.5f and -1.4f both round to 0.0. +class IncorrectRoundings : public ClangTidyCheck { +public: + IncorrectRoundings(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INCORRECTROUNDINGS_H_ diff --git a/clang-tidy/misc/InefficientAlgorithmCheck.cpp b/clang-tidy/misc/InefficientAlgorithmCheck.cpp new file mode 100644 index 000000000..a5a5b2b4f --- /dev/null +++ b/clang-tidy/misc/InefficientAlgorithmCheck.cpp @@ -0,0 +1,163 @@ +//===--- InefficientAlgorithmCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InefficientAlgorithmCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static bool areTypesCompatible(QualType Left, QualType Right) { + if (const auto *LeftRefType = Left->getAs()) + Left = LeftRefType->getPointeeType(); + if (const auto *RightRefType = Right->getAs()) + Right = RightRefType->getPointeeType(); + return Left->getCanonicalTypeUnqualified() == + Right->getCanonicalTypeUnqualified(); +} + +void InefficientAlgorithmCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto Algorithms = + hasAnyName("::std::find", "::std::count", "::std::equal_range", + "::std::lower_bound", "::std::upper_bound"); + const auto ContainerMatcher = classTemplateSpecializationDecl(hasAnyName( + "::std::set", "::std::map", "::std::multiset", "::std::multimap", + "::std::unordered_set", "::std::unordered_map", + "::std::unordered_multiset", "::std::unordered_multimap")); + + const auto Matcher = + callExpr( + callee(functionDecl(Algorithms)), + hasArgument( + 0, cxxConstructExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("begin"))), + on(declRefExpr( + hasDeclaration(decl().bind("IneffContObj")), + anyOf(hasType(ContainerMatcher.bind("IneffCont")), + hasType(pointsTo( + ContainerMatcher.bind("IneffContPtr"))))) + .bind("IneffContExpr"))))))), + hasArgument( + 1, cxxConstructExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("end"))), + on(declRefExpr( + hasDeclaration(equalsBoundNode("IneffContObj"))))))))), + hasArgument(2, expr().bind("AlgParam")), + unless(isInTemplateInstantiation())) + .bind("IneffAlg"); + + Finder->addMatcher(Matcher, this); +} + +void InefficientAlgorithmCheck::check(const MatchFinder::MatchResult &Result) { + const auto *AlgCall = Result.Nodes.getNodeAs("IneffAlg"); + const auto *IneffCont = + Result.Nodes.getNodeAs("IneffCont"); + bool PtrToContainer = false; + if (!IneffCont) { + IneffCont = + Result.Nodes.getNodeAs("IneffContPtr"); + PtrToContainer = true; + } + const llvm::StringRef IneffContName = IneffCont->getName(); + const bool Unordered = + IneffContName.find("unordered") != llvm::StringRef::npos; + const bool Maplike = IneffContName.find("map") != llvm::StringRef::npos; + + // Store if the key type of the container is compatible with the value + // that is searched for. + QualType ValueType = AlgCall->getArg(2)->getType(); + QualType KeyType = + IneffCont->getTemplateArgs()[0].getAsType().getCanonicalType(); + const bool CompatibleTypes = areTypesCompatible(KeyType, ValueType); + + // Check if the comparison type for the algorithm and the container matches. + if (AlgCall->getNumArgs() == 4 && !Unordered) { + const Expr *Arg = AlgCall->getArg(3); + const QualType AlgCmp = + Arg->getType().getUnqualifiedType().getCanonicalType(); + const unsigned CmpPosition = + (IneffContName.find("map") == llvm::StringRef::npos) ? 1 : 2; + const QualType ContainerCmp = IneffCont->getTemplateArgs()[CmpPosition] + .getAsType() + .getUnqualifiedType() + .getCanonicalType(); + if (AlgCmp != ContainerCmp) { + diag(Arg->getLocStart(), + "different comparers used in the algorithm and the container"); + return; + } + } + + const auto *AlgDecl = AlgCall->getDirectCallee(); + if (!AlgDecl) + return; + + if (Unordered && AlgDecl->getName().find("bound") != llvm::StringRef::npos) + return; + + const auto *AlgParam = Result.Nodes.getNodeAs("AlgParam"); + const auto *IneffContExpr = Result.Nodes.getNodeAs("IneffContExpr"); + FixItHint Hint; + + SourceManager &SM = *Result.SourceManager; + LangOptions LangOpts = getLangOpts(); + + CharSourceRange CallRange = + CharSourceRange::getTokenRange(AlgCall->getSourceRange()); + + // FIXME: Create a common utility to extract a file range that the given token + // sequence is exactly spelled at (without macro argument expansions etc.). + // We can't use Lexer::makeFileCharRange here, because for + // + // #define F(x) x + // x(a b c); + // + // it will return "x(a b c)", when given the range "a"-"c". It makes sense for + // removals, but not for replacements. + // + // This code is over-simplified, but works for many real cases. + if (SM.isMacroArgExpansion(CallRange.getBegin()) && + SM.isMacroArgExpansion(CallRange.getEnd())) { + CallRange.setBegin(SM.getSpellingLoc(CallRange.getBegin())); + CallRange.setEnd(SM.getSpellingLoc(CallRange.getEnd())); + } + + if (!CallRange.getBegin().isMacroID() && !Maplike && CompatibleTypes) { + StringRef ContainerText = Lexer::getSourceText( + CharSourceRange::getTokenRange(IneffContExpr->getSourceRange()), SM, + LangOpts); + StringRef ParamText = Lexer::getSourceText( + CharSourceRange::getTokenRange(AlgParam->getSourceRange()), SM, + LangOpts); + std::string ReplacementText = + (llvm::Twine(ContainerText) + (PtrToContainer ? "->" : ".") + + AlgDecl->getName() + "(" + ParamText + ")") + .str(); + Hint = FixItHint::CreateReplacement(CallRange, ReplacementText); + } + + diag(AlgCall->getLocStart(), + "this STL algorithm call should be replaced with a container method") + << Hint; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/InefficientAlgorithmCheck.h b/clang-tidy/misc/InefficientAlgorithmCheck.h new file mode 100644 index 000000000..6935b455e --- /dev/null +++ b/clang-tidy/misc/InefficientAlgorithmCheck.h @@ -0,0 +1,36 @@ +//===--- InefficientAlgorithmCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Warns on inefficient use of STL algorithms on associative containers. +/// +/// Associative containers implements some of the algorithms as methods which +/// should be preferred to the algorithms in the algorithm header. The methods +/// can take advanatage of the order of the elements. +class InefficientAlgorithmCheck : public ClangTidyCheck { +public: + InefficientAlgorithmCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_INEFFICIENTALGORITHMCHECK_H diff --git a/clang-tidy/misc/MacroParenthesesCheck.cpp b/clang-tidy/misc/MacroParenthesesCheck.cpp new file mode 100644 index 000000000..dd4df1cc4 --- /dev/null +++ b/clang-tidy/misc/MacroParenthesesCheck.cpp @@ -0,0 +1,260 @@ +//===--- MacroParenthesesCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MacroParenthesesCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +class MacroParenthesesPPCallbacks : public PPCallbacks { +public: + MacroParenthesesPPCallbacks(Preprocessor *PP, MacroParenthesesCheck *Check) + : PP(PP), Check(Check) {} + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + replacementList(MacroNameTok, MD->getMacroInfo()); + argument(MacroNameTok, MD->getMacroInfo()); + } + +private: + /// Replacement list with calculations should be enclosed in parentheses. + void replacementList(const Token &MacroNameTok, const MacroInfo *MI); + + /// Arguments should be enclosed in parentheses. + void argument(const Token &MacroNameTok, const MacroInfo *MI); + + Preprocessor *PP; + MacroParenthesesCheck *Check; +}; +} // namespace + +/// Is argument surrounded properly with parentheses/braces/squares/commas? +static bool isSurroundedLeft(const Token &T) { + return T.isOneOf(tok::l_paren, tok::l_brace, tok::l_square, tok::comma, + tok::semi); +} + +/// Is argument surrounded properly with parentheses/braces/squares/commas? +static bool isSurroundedRight(const Token &T) { + return T.isOneOf(tok::r_paren, tok::r_brace, tok::r_square, tok::comma, + tok::semi); +} + +/// Is given TokenKind a keyword? +static bool isKeyword(const Token &T) { + // FIXME: better matching of keywords to avoid false positives. + return T.isOneOf(tok::kw_case, tok::kw_const, tok::kw_struct); +} + +/// Warning is written when one of these operators are not within parentheses. +static bool isWarnOp(const Token &T) { + // FIXME: This is an initial list of operators. It can be tweaked later to + // get more positives or perhaps avoid some false positive. + return T.isOneOf(tok::plus, tok::minus, tok::star, tok::slash, tok::percent, + tok::amp, tok::pipe, tok::caret); +} + +/// Is given Token a keyword that is used in variable declarations? +static bool isVarDeclKeyword(const Token &T) { + return T.isOneOf(tok::kw_bool, tok::kw_char, tok::kw_short, tok::kw_int, + tok::kw_long, tok::kw_float, tok::kw_double, tok::kw_const, + tok::kw_enum, tok::kw_inline, tok::kw_static, tok::kw_struct, + tok::kw_signed, tok::kw_unsigned); +} + +/// Is there a possible variable declaration at Tok? +static bool possibleVarDecl(const MacroInfo *MI, const Token *Tok) { + if (Tok == MI->tokens_end()) + return false; + + // If we see int/short/struct/etc., just assume this is a variable + // declaration. + if (isVarDeclKeyword(*Tok)) + return true; + + // Variable declarations start with identifier or coloncolon. + if (!Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon)) + return false; + + // Skip possible types, etc + while (Tok != MI->tokens_end() && + Tok->isOneOf(tok::identifier, tok::raw_identifier, tok::coloncolon, + tok::star, tok::amp, tok::ampamp, tok::less, + tok::greater)) + Tok++; + + // Return true for possible variable declarations. + return Tok == MI->tokens_end() || + Tok->isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren) || + isVarDeclKeyword(*Tok); +} + +void MacroParenthesesPPCallbacks::replacementList(const Token &MacroNameTok, + const MacroInfo *MI) { + // Make sure macro replacement isn't a variable declaration. + if (possibleVarDecl(MI, MI->tokens_begin())) + return; + + // Count how deep we are in parentheses/braces/squares. + int Count = 0; + + // SourceLocation for error + SourceLocation Loc; + + for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) { + const Token &Tok = *TI; + // Replacement list contains keywords, don't warn about it. + if (isKeyword(Tok)) + return; + // When replacement list contains comma/semi don't warn about it. + if (Count == 0 && Tok.isOneOf(tok::comma, tok::semi)) + return; + if (Tok.isOneOf(tok::l_paren, tok::l_brace, tok::l_square)) { + ++Count; + } else if (Tok.isOneOf(tok::r_paren, tok::r_brace, tok::r_square)) { + --Count; + // If there are unbalanced parentheses don't write any warning + if (Count < 0) + return; + } else if (Count == 0 && isWarnOp(Tok)) { + // Heuristic for macros that are clearly not intended to be enclosed in + // parentheses, macro starts with operator. For example: + // #define X *10 + if (TI == MI->tokens_begin() && (TI + 1) != TE && + !Tok.isOneOf(tok::plus, tok::minus)) + return; + // Don't warn about this macro if the last token is a star. For example: + // #define X void * + if ((TE - 1)->is(tok::star)) + return; + + Loc = Tok.getLocation(); + } + } + if (Loc.isValid()) { + const Token &Last = *(MI->tokens_end() - 1); + Check->diag(Loc, "macro replacement list should be enclosed in parentheses") + << FixItHint::CreateInsertion(MI->tokens_begin()->getLocation(), "(") + << FixItHint::CreateInsertion(Last.getLocation().getLocWithOffset( + PP->getSpelling(Last).length()), + ")"); + } +} + +void MacroParenthesesPPCallbacks::argument(const Token &MacroNameTok, + const MacroInfo *MI) { + + // Skip variable declaration. + bool VarDecl = possibleVarDecl(MI, MI->tokens_begin()); + + for (auto TI = MI->tokens_begin(), TE = MI->tokens_end(); TI != TE; ++TI) { + // First token. + if (TI == MI->tokens_begin()) + continue; + + // Last token. + if ((TI + 1) == MI->tokens_end()) + continue; + + const Token &Prev = *(TI - 1); + const Token &Next = *(TI + 1); + + const Token &Tok = *TI; + + // There should not be extra parentheses in possible variable declaration. + if (VarDecl) { + if (Tok.isOneOf(tok::equal, tok::semi, tok::l_square, tok::l_paren)) + VarDecl = false; + continue; + } + + // Only interested in identifiers. + if (!Tok.isOneOf(tok::identifier, tok::raw_identifier)) + continue; + + // Only interested in macro arguments. + if (MI->getArgumentNum(Tok.getIdentifierInfo()) < 0) + continue; + + // Argument is surrounded with parentheses/squares/braces/commas. + if (isSurroundedLeft(Prev) && isSurroundedRight(Next)) + continue; + + // Don't warn after hash/hashhash or before hashhash. + if (Prev.isOneOf(tok::hash, tok::hashhash) || Next.is(tok::hashhash)) + continue; + + // Argument is a struct member. + if (Prev.isOneOf(tok::period, tok::arrow, tok::coloncolon, tok::arrowstar, + tok::periodstar)) + continue; + + // Argument is a namespace or class. + if (Next.is(tok::coloncolon)) + continue; + + // String concatenation. + if (isStringLiteral(Prev.getKind()) || isStringLiteral(Next.getKind())) + continue; + + // Type/Var. + if (isAnyIdentifier(Prev.getKind()) || isKeyword(Prev) || + isAnyIdentifier(Next.getKind()) || isKeyword(Next)) + continue; + + // Initialization. + if (Next.is(tok::l_paren)) + continue; + + // Cast. + if (Prev.is(tok::l_paren) && Next.is(tok::star) && + TI + 2 != MI->tokens_end() && (TI + 2)->is(tok::r_paren)) + continue; + + // Assignment/return, i.e. '=x;' or 'return x;'. + if (Prev.isOneOf(tok::equal, tok::kw_return) && Next.is(tok::semi)) + continue; + + // C++ template parameters. + if (PP->getLangOpts().CPlusPlus && Prev.isOneOf(tok::comma, tok::less) && + Next.isOneOf(tok::comma, tok::greater)) + continue; + + // Namespaces. + if (Prev.is(tok::kw_namespace)) + continue; + + // Variadic templates + if (MI->isVariadic()) + continue; + + Check->diag(Tok.getLocation(), "macro argument should be enclosed in " + "parentheses") + << FixItHint::CreateInsertion(Tok.getLocation(), "(") + << FixItHint::CreateInsertion(Tok.getLocation().getLocWithOffset( + PP->getSpelling(Tok).length()), + ")"); + } +} + +void MacroParenthesesCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique( + &Compiler.getPreprocessor(), this)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MacroParenthesesCheck.h b/clang-tidy/misc/MacroParenthesesCheck.h new file mode 100644 index 000000000..e398fc656 --- /dev/null +++ b/clang-tidy/misc/MacroParenthesesCheck.h @@ -0,0 +1,43 @@ +//===--- MacroParenthesesCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds macros that can have unexpected behaviour due to missing parentheses. +/// +/// Macros are expanded by the preprocessor as-is. As a result, there can be +/// unexpected behaviour; operators may be evaluated in unexpected order and +/// unary operators may become binary operators, etc. +/// +/// When the replacement list has an expression, it is recommended to surround +/// it with parentheses. This ensures that the macro result is evaluated +/// completely before it is used. +/// +/// It is also recommended to surround macro arguments in the replacement list +/// with parentheses. This ensures that the argument value is calculated +/// properly. +class MacroParenthesesCheck : public ClangTidyCheck { +public: + MacroParenthesesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_PARENTHESES_H diff --git a/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp new file mode 100644 index 000000000..229ec850b --- /dev/null +++ b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.cpp @@ -0,0 +1,184 @@ +//===--- MacroRepeatedSideEffectsCheck.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MacroRepeatedSideEffectsCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/MacroArgs.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +class MacroRepeatedPPCallbacks : public PPCallbacks { +public: + MacroRepeatedPPCallbacks(ClangTidyCheck &Check, Preprocessor &PP) + : Check(Check), PP(PP) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override; + +private: + ClangTidyCheck &Check; + Preprocessor &PP; + + unsigned countArgumentExpansions(const MacroInfo *MI, + const IdentifierInfo *Arg) const; + + bool hasSideEffects(const Token *ResultArgToks) const; +}; +} // End of anonymous namespace. + +void MacroRepeatedPPCallbacks::MacroExpands(const Token &MacroNameTok, + const MacroDefinition &MD, + SourceRange Range, + const MacroArgs *Args) { + // Ignore macro argument expansions. + if (!Range.getBegin().isFileID()) + return; + + const MacroInfo *MI = MD.getMacroInfo(); + + // Bail out if the contents of the macro are containing keywords that are + // making the macro too complex. + if (std::find_if( + MI->tokens().begin(), MI->tokens().end(), [](const Token &T) { + return T.isOneOf(tok::kw_if, tok::kw_else, tok::kw_switch, + tok::kw_case, tok::kw_break, tok::kw_while, + tok::kw_do, tok::kw_for, tok::kw_continue, + tok::kw_goto, tok::kw_return); + }) != MI->tokens().end()) + return; + + for (unsigned ArgNo = 0U; ArgNo < MI->getNumArgs(); ++ArgNo) { + const IdentifierInfo *Arg = *(MI->arg_begin() + ArgNo); + const Token *ResultArgToks = Args->getUnexpArgument(ArgNo); + + if (hasSideEffects(ResultArgToks) && + countArgumentExpansions(MI, Arg) >= 2) { + Check.diag(ResultArgToks->getLocation(), + "side effects in the %ordinal0 macro argument %1 are " + "repeated in macro expansion") + << (ArgNo + 1) << Arg; + Check.diag(MI->getDefinitionLoc(), "macro %0 defined here", + DiagnosticIDs::Note) + << MacroNameTok.getIdentifierInfo(); + } + } +} + +unsigned MacroRepeatedPPCallbacks::countArgumentExpansions( + const MacroInfo *MI, const IdentifierInfo *Arg) const { + // Current argument count. When moving forward to a different control-flow + // path this can decrease. + unsigned Current = 0; + // Max argument count. + unsigned Max = 0; + bool SkipParen = false; + int SkipParenCount = 0; + // Has a __builtin_constant_p been found? + bool FoundBuiltin = false; + bool PrevTokenIsHash = false; + // Count when "?" is reached. The "Current" will get this value when the ":" + // is reached. + std::stack> CountAtQuestion; + for (const auto &T : MI->tokens()) { + // The result of __builtin_constant_p(x) is 0 if x is a macro argument + // with side effects. If we see a __builtin_constant_p(x) followed by a + // "?" "&&" or "||", then we need to reason about control flow to report + // warnings correctly. Until such reasoning is added, bail out when this + // happens. + if (FoundBuiltin && T.isOneOf(tok::question, tok::ampamp, tok::pipepipe)) + return Max; + + // Skip stringified tokens. + if (T.is(tok::hash)) { + PrevTokenIsHash = true; + continue; + } + if (PrevTokenIsHash) { + PrevTokenIsHash = false; + continue; + } + + // Handling of ? and :. + if (T.is(tok::question)) { + CountAtQuestion.push(Current); + } else if (T.is(tok::colon)) { + if (CountAtQuestion.empty()) + return 0; + Current = CountAtQuestion.top(); + CountAtQuestion.pop(); + } + + // If current token is a parenthesis, skip it. + if (SkipParen) { + if (T.is(tok::l_paren)) + SkipParenCount++; + else if (T.is(tok::r_paren)) + SkipParenCount--; + SkipParen = (SkipParenCount != 0); + if (SkipParen) + continue; + } + + IdentifierInfo *TII = T.getIdentifierInfo(); + // If not existent, skip it. + if (TII == nullptr) + continue; + + // If a __builtin_constant_p is found within the macro definition, don't + // count arguments inside the parentheses and remember that it has been + // seen in case there are "?", "&&" or "||" operators later. + if (TII->getBuiltinID() == Builtin::BI__builtin_constant_p) { + FoundBuiltin = true; + SkipParen = true; + continue; + } + + // If another macro is found within the macro definition, skip the macro + // and the eventual arguments. + if (TII->hasMacroDefinition()) { + const MacroInfo *M = PP.getMacroDefinition(TII).getMacroInfo(); + if (M != nullptr && M->isFunctionLike()) + SkipParen = true; + continue; + } + + // Count argument. + if (TII == Arg) { + Current++; + if (Current > Max) + Max = Current; + } + } + return Max; +} + +bool MacroRepeatedPPCallbacks::hasSideEffects( + const Token *ResultArgToks) const { + for (; ResultArgToks->isNot(tok::eof); ++ResultArgToks) { + if (ResultArgToks->isOneOf(tok::plusplus, tok::minusminus)) + return true; + } + return false; +} + +void MacroRepeatedSideEffectsCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique( + *this, Compiler.getPreprocessor())); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h new file mode 100644 index 000000000..10ff8427b --- /dev/null +++ b/clang-tidy/misc/MacroRepeatedSideEffectsCheck.h @@ -0,0 +1,31 @@ +//===--- MacroRepeatedSideEffectsCheck.h - clang-tidy -----------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks for repeated argument with side effects in macros. +class MacroRepeatedSideEffectsCheck : public ClangTidyCheck { +public: + MacroRepeatedSideEffectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MACRO_REPEATED_SIDE_EFFECTS_CHECK_H diff --git a/clang-tidy/misc/MiscTidyModule.cpp b/clang-tidy/misc/MiscTidyModule.cpp new file mode 100644 index 000000000..755e2c7ad --- /dev/null +++ b/clang-tidy/misc/MiscTidyModule.cpp @@ -0,0 +1,157 @@ +//===--- MiscTidyModule.cpp - clang-tidy ----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "ArgumentCommentCheck.h" +#include "AssertSideEffectCheck.h" +#include "BoolPointerImplicitConversionCheck.h" +#include "DanglingHandleCheck.h" +#include "DefinitionsInHeadersCheck.h" +#include "FoldInitTypeCheck.h" +#include "ForwardDeclarationNamespaceCheck.h" +#include "InaccurateEraseCheck.h" +#include "IncorrectRoundings.h" +#include "InefficientAlgorithmCheck.h" +#include "MacroParenthesesCheck.h" +#include "MacroRepeatedSideEffectsCheck.h" +#include "MisplacedConstCheck.h" +#include "MisplacedWideningCastCheck.h" +#include "MoveConstantArgumentCheck.h" +#include "MoveConstructorInitCheck.h" +#include "MoveForwardingReferenceCheck.h" +#include "MultipleStatementMacroCheck.h" +#include "NewDeleteOverloadsCheck.h" +#include "NoexceptMoveConstructorCheck.h" +#include "NonCopyableObjects.h" +#include "RedundantExpressionCheck.h" +#include "SizeofContainerCheck.h" +#include "SizeofExpressionCheck.h" +#include "StaticAssertCheck.h" +#include "StringCompareCheck.h" +#include "StringConstructorCheck.h" +#include "StringIntegerAssignmentCheck.h" +#include "StringLiteralWithEmbeddedNulCheck.h" +#include "SuspiciousEnumUsageCheck.h" +#include "SuspiciousMissingCommaCheck.h" +#include "SuspiciousSemicolonCheck.h" +#include "SuspiciousStringCompareCheck.h" +#include "SwappedArgumentsCheck.h" +#include "ThrowByValueCatchByReferenceCheck.h" +#include "UnconventionalAssignOperatorCheck.h" +#include "UndelegatedConstructor.h" +#include "UniqueptrResetReleaseCheck.h" +#include "UnusedAliasDeclsCheck.h" +#include "UnusedParametersCheck.h" +#include "UnusedRAIICheck.h" +#include "UnusedUsingDeclsCheck.h" +#include "UseAfterMoveCheck.h" +#include "VirtualNearMissCheck.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MiscModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("misc-argument-comment"); + CheckFactories.registerCheck( + "misc-assert-side-effect"); + CheckFactories.registerCheck("misc-misplaced-const"); + CheckFactories.registerCheck( + "misc-unconventional-assign-operator"); + CheckFactories.registerCheck( + "misc-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck("misc-dangling-handle"); + CheckFactories.registerCheck( + "misc-definitions-in-headers"); + CheckFactories.registerCheck("misc-fold-init-type"); + CheckFactories.registerCheck( + "misc-forward-declaration-namespace"); + CheckFactories.registerCheck("misc-inaccurate-erase"); + CheckFactories.registerCheck( + "misc-incorrect-roundings"); + CheckFactories.registerCheck( + "misc-inefficient-algorithm"); + CheckFactories.registerCheck( + "misc-macro-parentheses"); + CheckFactories.registerCheck( + "misc-macro-repeated-side-effects"); + CheckFactories.registerCheck( + "misc-misplaced-widening-cast"); + CheckFactories.registerCheck( + "misc-move-const-arg"); + CheckFactories.registerCheck( + "misc-move-constructor-init"); + CheckFactories.registerCheck( + "misc-move-forwarding-reference"); + CheckFactories.registerCheck( + "misc-multiple-statement-macro"); + CheckFactories.registerCheck( + "misc-new-delete-overloads"); + CheckFactories.registerCheck( + "misc-noexcept-move-constructor"); + CheckFactories.registerCheck( + "misc-non-copyable-objects"); + CheckFactories.registerCheck( + "misc-redundant-expression"); + CheckFactories.registerCheck("misc-sizeof-container"); + CheckFactories.registerCheck( + "misc-sizeof-expression"); + CheckFactories.registerCheck("misc-static-assert"); + CheckFactories.registerCheck("misc-string-compare"); + CheckFactories.registerCheck( + "misc-string-constructor"); + CheckFactories.registerCheck( + "misc-string-integer-assignment"); + CheckFactories.registerCheck( + "misc-string-literal-with-embedded-nul"); + CheckFactories.registerCheck( + "misc-suspicious-enum-usage"); + CheckFactories.registerCheck( + "misc-suspicious-missing-comma"); + CheckFactories.registerCheck( + "misc-suspicious-semicolon"); + CheckFactories.registerCheck( + "misc-suspicious-string-compare"); + CheckFactories.registerCheck( + "misc-swapped-arguments"); + CheckFactories.registerCheck( + "misc-throw-by-value-catch-by-reference"); + CheckFactories.registerCheck( + "misc-undelegated-constructor"); + CheckFactories.registerCheck( + "misc-uniqueptr-reset-release"); + CheckFactories.registerCheck( + "misc-unused-alias-decls"); + CheckFactories.registerCheck( + "misc-unused-parameters"); + CheckFactories.registerCheck("misc-unused-raii"); + CheckFactories.registerCheck( + "misc-unused-using-decls"); + CheckFactories.registerCheck("misc-use-after-move"); + CheckFactories.registerCheck( + "misc-virtual-near-miss"); + } +}; + +} // namespace misc + +// Register the MiscTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("misc-module", "Adds miscellaneous lint checks."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the MiscModule. +volatile int MiscModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MisplacedConstCheck.cpp b/clang-tidy/misc/MisplacedConstCheck.cpp new file mode 100644 index 000000000..515b22c0c --- /dev/null +++ b/clang-tidy/misc/MisplacedConstCheck.cpp @@ -0,0 +1,63 @@ +//===--- MisplacedConstCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisplacedConstCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void MisplacedConstCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + valueDecl(hasType(isConstQualified()), + hasType(typedefType(hasDeclaration( + typedefDecl(hasType(pointerType(unless(pointee( + anyOf(isConstQualified(), + ignoringParens(functionType()))))))) + .bind("typedef"))))) + .bind("decl"), + this); +} + +static QualType guessAlternateQualification(ASTContext &Context, QualType QT) { + // We're given a QualType from a typedef where the qualifiers apply to the + // pointer instead of the pointee. Strip the const qualifier from the pointer + // type and add it to the pointee instead. + if (!QT->isPointerType()) + return QT; + + Qualifiers Quals = QT.getLocalQualifiers(); + Quals.removeConst(); + + QualType NewQT = Context.getPointerType( + QualType(QT->getPointeeType().getTypePtr(), Qualifiers::Const)); + return NewQT.withCVRQualifiers(Quals.getCVRQualifiers()); +} + +void MisplacedConstCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Var = Result.Nodes.getNodeAs("decl"); + const auto *Typedef = Result.Nodes.getNodeAs("typedef"); + ASTContext &Ctx = *Result.Context; + QualType CanQT = Var->getType().getCanonicalType(); + + diag(Var->getLocation(), "%0 declared with a const-qualified typedef type; " + "results in the type being '%1' instead of '%2'") + << Var << CanQT.getAsString(Ctx.getPrintingPolicy()) + << guessAlternateQualification(Ctx, CanQT) + .getAsString(Ctx.getPrintingPolicy()); + diag(Typedef->getLocation(), "typedef declared here", DiagnosticIDs::Note); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MisplacedConstCheck.h b/clang-tidy/misc/MisplacedConstCheck.h new file mode 100644 index 000000000..410edf7b7 --- /dev/null +++ b/clang-tidy/misc/MisplacedConstCheck.h @@ -0,0 +1,36 @@ +//===--- MisplacedConstCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_CONST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_CONST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check diagnoses when a const qualifier is applied to a typedef to a +/// pointer type rather than to the pointee. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-misplaced-const.html +class MisplacedConstCheck : public ClangTidyCheck { +public: + MisplacedConstCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_CONST_H diff --git a/clang-tidy/misc/MisplacedWideningCastCheck.cpp b/clang-tidy/misc/MisplacedWideningCastCheck.cpp new file mode 100644 index 000000000..c49f96a06 --- /dev/null +++ b/clang-tidy/misc/MisplacedWideningCastCheck.cpp @@ -0,0 +1,229 @@ +//===--- MisplacedWideningCastCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisplacedWideningCastCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +MisplacedWideningCastCheck::MisplacedWideningCastCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckImplicitCasts(Options.get("CheckImplicitCasts", true)) {} + +void MisplacedWideningCastCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts); +} + +void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) { + const auto Calc = + expr(anyOf(binaryOperator( + anyOf(hasOperatorName("+"), hasOperatorName("-"), + hasOperatorName("*"), hasOperatorName("<<"))), + unaryOperator(hasOperatorName("~"))), + hasType(isInteger())) + .bind("Calc"); + + const auto ExplicitCast = explicitCastExpr(hasDestinationType(isInteger()), + has(ignoringParenImpCasts(Calc))); + const auto ImplicitCast = + implicitCastExpr(hasImplicitDestinationType(isInteger()), + has(ignoringParenImpCasts(Calc))); + const auto Cast = expr(anyOf(ExplicitCast, ImplicitCast)).bind("Cast"); + + Finder->addMatcher(varDecl(hasInitializer(Cast)), this); + Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this); + Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this); + Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this); + Finder->addMatcher( + binaryOperator(matchers::isComparisonOperator(), hasEitherOperand(Cast)), + this); +} + +static unsigned getMaxCalculationWidth(const ASTContext &Context, + const Expr *E) { + E = E->IgnoreParenImpCasts(); + + if (const auto *Bop = dyn_cast(E)) { + unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS()); + unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS()); + if (Bop->getOpcode() == BO_Mul) + return LHSWidth + RHSWidth; + if (Bop->getOpcode() == BO_Add) + return std::max(LHSWidth, RHSWidth) + 1; + if (Bop->getOpcode() == BO_Rem) { + llvm::APSInt Val; + if (Bop->getRHS()->EvaluateAsInt(Val, Context)) + return Val.getActiveBits(); + } else if (Bop->getOpcode() == BO_Shl) { + llvm::APSInt Bits; + if (Bop->getRHS()->EvaluateAsInt(Bits, Context)) { + // We don't handle negative values and large values well. It is assumed + // that compiler warnings are written for such values so the user will + // fix that. + return LHSWidth + Bits.getExtValue(); + } + + // Unknown bitcount, assume there is truncation. + return 1024U; + } + } else if (const auto *Uop = dyn_cast(E)) { + // There is truncation when ~ is used. + if (Uop->getOpcode() == UO_Not) + return 1024U; + + QualType T = Uop->getType(); + return T->isIntegerType() ? Context.getIntWidth(T) : 1024U; + } else if (const auto *I = dyn_cast(E)) { + return I->getValue().getActiveBits(); + } + + return Context.getIntWidth(E->getType()); +} + +static int relativeIntSizes(BuiltinType::Kind Kind) { + switch (Kind) { + case BuiltinType::UChar: + return 1; + case BuiltinType::SChar: + return 1; + case BuiltinType::Char_U: + return 1; + case BuiltinType::Char_S: + return 1; + case BuiltinType::UShort: + return 2; + case BuiltinType::Short: + return 2; + case BuiltinType::UInt: + return 3; + case BuiltinType::Int: + return 3; + case BuiltinType::ULong: + return 4; + case BuiltinType::Long: + return 4; + case BuiltinType::ULongLong: + return 5; + case BuiltinType::LongLong: + return 5; + case BuiltinType::UInt128: + return 6; + case BuiltinType::Int128: + return 6; + default: + return 0; + } +} + +static int relativeCharSizes(BuiltinType::Kind Kind) { + switch (Kind) { + case BuiltinType::UChar: + return 1; + case BuiltinType::SChar: + return 1; + case BuiltinType::Char_U: + return 1; + case BuiltinType::Char_S: + return 1; + case BuiltinType::Char16: + return 2; + case BuiltinType::Char32: + return 3; + default: + return 0; + } +} + +static int relativeCharSizesW(BuiltinType::Kind Kind) { + switch (Kind) { + case BuiltinType::UChar: + return 1; + case BuiltinType::SChar: + return 1; + case BuiltinType::Char_U: + return 1; + case BuiltinType::Char_S: + return 1; + case BuiltinType::WChar_U: + return 2; + case BuiltinType::WChar_S: + return 2; + default: + return 0; + } +} + +static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) { + int FirstSize, SecondSize; + if ((FirstSize = relativeIntSizes(First)) != 0 && + (SecondSize = relativeIntSizes(Second)) != 0) + return FirstSize > SecondSize; + if ((FirstSize = relativeCharSizes(First)) != 0 && + (SecondSize = relativeCharSizes(Second)) != 0) + return FirstSize > SecondSize; + if ((FirstSize = relativeCharSizesW(First)) != 0 && + (SecondSize = relativeCharSizesW(Second)) != 0) + return FirstSize > SecondSize; + return false; +} + +void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Cast = Result.Nodes.getNodeAs("Cast"); + if (!CheckImplicitCasts && isa(Cast)) + return; + if (Cast->getLocStart().isMacroID()) + return; + + const auto *Calc = Result.Nodes.getNodeAs("Calc"); + if (Calc->getLocStart().isMacroID()) + return; + + ASTContext &Context = *Result.Context; + + QualType CastType = Cast->getType(); + QualType CalcType = Calc->getType(); + + // Explicit truncation using cast. + if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType)) + return; + + // If CalcType and CastType have same size then there is no real danger, but + // there can be a portability problem. + + if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) { + const auto *CastBuiltinType = + dyn_cast(CastType->getUnqualifiedDesugaredType()); + const auto *CalcBuiltinType = + dyn_cast(CalcType->getUnqualifiedDesugaredType()); + if (CastBuiltinType && CalcBuiltinType && + !isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind())) + return; + } + + // Don't write a warning if we can easily see that the result is not + // truncated. + if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc)) + return; + + diag(Cast->getLocStart(), "either cast from %0 to %1 is ineffective, or " + "there is loss of precision before the conversion") + << CalcType << CastType; +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MisplacedWideningCastCheck.h b/clang-tidy/misc/MisplacedWideningCastCheck.h new file mode 100644 index 000000000..1c3bc4a11 --- /dev/null +++ b/clang-tidy/misc/MisplacedWideningCastCheck.h @@ -0,0 +1,46 @@ +//===--- MisplacedWideningCastCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_WIDENING_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MISPLACED_WIDENING_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find casts of calculation results to bigger type. Typically from int to +/// long. If the intention of the cast is to avoid loss of precision then +/// the cast is misplaced, and there can be loss of precision. Otherwise +/// such cast is ineffective. +/// +/// There is one option: +/// +/// - `CheckImplicitCasts`: Whether to check implicit casts as well which may +// be the most common case. Enabled by default. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-misplaced-widening-cast.html +class MisplacedWideningCastCheck : public ClangTidyCheck { +public: + MisplacedWideningCastCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool CheckImplicitCasts; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif diff --git a/clang-tidy/misc/MoveConstantArgumentCheck.cpp b/clang-tidy/misc/MoveConstantArgumentCheck.cpp new file mode 100644 index 000000000..058ad2b8b --- /dev/null +++ b/clang-tidy/misc/MoveConstantArgumentCheck.cpp @@ -0,0 +1,101 @@ +//===--- MoveConstantArgumentCheck.cpp - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveConstantArgumentCheck.h" + +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static void ReplaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag, + const SourceManager &SM, + const LangOptions &LangOpts) { + const Expr *Arg = Call->getArg(0); + + CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(Call->getLocStart(), Arg->getLocStart()), + SM, LangOpts); + CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(Call->getLocEnd(), + Call->getLocEnd().getLocWithOffset(1)), + SM, LangOpts); + + if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { + Diag << FixItHint::CreateRemoval(BeforeArgumentsRange) + << FixItHint::CreateRemoval(AfterArgumentsRange); + } +} + +void MoveConstantArgumentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto MoveCallMatcher = + callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1), + unless(isInTemplateInstantiation())) + .bind("call-move"); + + Finder->addMatcher(MoveCallMatcher, this); + + auto ConstParamMatcher = forEachArgumentWithParam( + MoveCallMatcher, parmVarDecl(hasType(references(isConstQualified())))); + + Finder->addMatcher(callExpr(ConstParamMatcher).bind("receiving-expr"), this); + Finder->addMatcher(cxxConstructExpr(ConstParamMatcher).bind("receiving-expr"), + this); +} + +void MoveConstantArgumentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CallMove = Result.Nodes.getNodeAs("call-move"); + const auto *ReceivingExpr = Result.Nodes.getNodeAs("receiving-expr"); + const Expr *Arg = CallMove->getArg(0); + SourceManager &SM = Result.Context->getSourceManager(); + + CharSourceRange MoveRange = + CharSourceRange::getCharRange(CallMove->getSourceRange()); + CharSourceRange FileMoveRange = + Lexer::makeFileCharRange(MoveRange, SM, getLangOpts()); + if (!FileMoveRange.isValid()) + return; + + bool IsConstArg = Arg->getType().isConstQualified(); + bool IsTriviallyCopyable = + Arg->getType().isTriviallyCopyableType(*Result.Context); + + if (IsConstArg || IsTriviallyCopyable) { + bool IsVariable = isa(Arg); + const auto *Var = + IsVariable ? dyn_cast(Arg)->getDecl() : nullptr; + auto Diag = diag(FileMoveRange.getBegin(), + "std::move of the %select{|const }0" + "%select{expression|variable %4}1 " + "%select{|of the trivially-copyable type %5 }2" + "has no effect; remove std::move()" + "%select{| or make the variable non-const}3") + << IsConstArg << IsVariable << IsTriviallyCopyable + << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var + << Arg->getType(); + + ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); + } else if (ReceivingExpr) { + auto Diag = diag(FileMoveRange.getBegin(), + "passing result of std::move() as a const reference " + "argument; no move will actually happen"); + + ReplaceCallWithArg(CallMove, Diag, SM, getLangOpts()); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstantArgumentCheck.h b/clang-tidy/misc/MoveConstantArgumentCheck.h new file mode 100644 index 000000000..27aa06f9f --- /dev/null +++ b/clang-tidy/misc/MoveConstantArgumentCheck.h @@ -0,0 +1,31 @@ +//===--- MoveConstantArgumentCheck.h - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +class MoveConstantArgumentCheck : public ClangTidyCheck { +public: + MoveConstantArgumentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H diff --git a/clang-tidy/misc/MoveConstructorInitCheck.cpp b/clang-tidy/misc/MoveConstructorInitCheck.cpp new file mode 100644 index 000000000..5d0987229 --- /dev/null +++ b/clang-tidy/misc/MoveConstructorInitCheck.cpp @@ -0,0 +1,107 @@ +//===--- MoveConstructorInitCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveConstructorInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +MoveConstructorInitCheck::MoveConstructorInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void MoveConstructorInitCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxConstructorDecl( + unless(isImplicit()), + allOf(isMoveConstructor(), + hasAnyConstructorInitializer( + cxxCtorInitializer( + withInitializer(cxxConstructExpr(hasDeclaration( + cxxConstructorDecl(isCopyConstructor()) + .bind("ctor"))))) + .bind("move-init")))), + this); +} + +void MoveConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CopyCtor = Result.Nodes.getNodeAs("ctor"); + const auto *Initializer = + Result.Nodes.getNodeAs("move-init"); + + // Do not diagnose if the expression used to perform the initialization is a + // trivially-copyable type. + QualType QT = Initializer->getInit()->getType(); + if (QT.isTriviallyCopyableType(*Result.Context)) + return; + + const auto *RD = QT->getAsCXXRecordDecl(); + if (RD && RD->isTriviallyCopyable()) + return; + + // Diagnose when the class type has a move constructor available, but the + // ctor-initializer uses the copy constructor instead. + const CXXConstructorDecl *Candidate = nullptr; + for (const auto *Ctor : CopyCtor->getParent()->ctors()) { + if (Ctor->isMoveConstructor() && Ctor->getAccess() <= AS_protected && + !Ctor->isDeleted()) { + // The type has a move constructor that is at least accessible to the + // initializer. + // + // FIXME: Determine whether the move constructor is a viable candidate + // for the ctor-initializer, perhaps provide a fixit that suggests + // using std::move(). + Candidate = Ctor; + break; + } + } + + if (Candidate) { + // There's a move constructor candidate that the caller probably intended + // to call instead. + diag(Initializer->getSourceLocation(), + "move constructor initializes %0 by calling a copy constructor") + << (Initializer->isBaseInitializer() ? "base class" : "class member"); + diag(CopyCtor->getLocation(), "copy constructor being called", + DiagnosticIDs::Note); + diag(Candidate->getLocation(), "candidate move constructor here", + DiagnosticIDs::Note); + } +} + +void MoveConstructorInitCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void MoveConstructorInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveConstructorInitCheck.h b/clang-tidy/misc/MoveConstructorInitCheck.h new file mode 100644 index 000000000..adfba029c --- /dev/null +++ b/clang-tidy/misc/MoveConstructorInitCheck.h @@ -0,0 +1,44 @@ +//===--- MoveConstructorInitCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// The check flags user-defined move constructors that have a ctor-initializer +/// initializing a member or base class through a copy constructor instead of a +/// move constructor. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-move-constructor-init.html +class MoveConstructorInitCheck : public ClangTidyCheck { +public: + MoveConstructorInitCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTRUCTORINITCHECK_H diff --git a/clang-tidy/misc/MoveForwardingReferenceCheck.cpp b/clang-tidy/misc/MoveForwardingReferenceCheck.cpp new file mode 100644 index 000000000..12c19dd9c --- /dev/null +++ b/clang-tidy/misc/MoveForwardingReferenceCheck.cpp @@ -0,0 +1,133 @@ +//===--- MoveForwardingReferenceCheck.cpp - clang-tidy --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MoveForwardingReferenceCheck.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/raw_ostream.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static void replaceMoveWithForward(const UnresolvedLookupExpr *Callee, + const ParmVarDecl *ParmVar, + const TemplateTypeParmDecl *TypeParmDecl, + DiagnosticBuilder &Diag, + const ASTContext &Context) { + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + CharSourceRange CallRange = + Lexer::makeFileCharRange(CharSourceRange::getTokenRange( + Callee->getLocStart(), Callee->getLocEnd()), + SM, LangOpts); + + if (CallRange.isValid()) { + const std::string TypeName = + TypeParmDecl->getIdentifier() + ? TypeParmDecl->getName().str() + : (llvm::Twine("decltype(") + ParmVar->getName() + ")").str(); + + const std::string ForwardName = + (llvm::Twine("forward<") + TypeName + ">").str(); + + // Create a replacement only if we see a "standard" way of calling + // std::move(). This will hopefully prevent erroneous replacements if the + // code does unusual things (e.g. create an alias for std::move() in + // another namespace). + NestedNameSpecifier *NNS = Callee->getQualifier(); + if (!NNS) { + // Called as "move" (i.e. presumably the code had a "using std::move;"). + // We still conservatively put a "std::" in front of the forward because + // we don't know whether the code also had a "using std::forward;". + Diag << FixItHint::CreateReplacement(CallRange, "std::" + ForwardName); + } else if (const NamespaceDecl *Namespace = NNS->getAsNamespace()) { + if (Namespace->getName() == "std") { + if (!NNS->getPrefix()) { + // Called as "std::move". + Diag << FixItHint::CreateReplacement(CallRange, + "std::" + ForwardName); + } else if (NNS->getPrefix()->getKind() == NestedNameSpecifier::Global) { + // Called as "::std::move". + Diag << FixItHint::CreateReplacement(CallRange, + "::std::" + ForwardName); + } + } + } + } +} + +void MoveForwardingReferenceCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // Matches a ParmVarDecl for a forwarding reference, i.e. a non-const rvalue + // reference of a function template parameter type. + auto ForwardingReferenceParmMatcher = + parmVarDecl( + hasType(qualType(rValueReferenceType(), + references(templateTypeParmType(hasDeclaration( + templateTypeParmDecl().bind("type-parm-decl")))), + unless(references(qualType(isConstQualified())))))) + .bind("parm-var"); + + Finder->addMatcher( + callExpr(callee(unresolvedLookupExpr( + hasAnyDeclaration(namedDecl( + hasUnderlyingDecl(hasName("::std::move"))))) + .bind("lookup")), + argumentCountIs(1), + hasArgument(0, ignoringParenImpCasts(declRefExpr( + to(ForwardingReferenceParmMatcher))))) + .bind("call-move"), + this); +} + +void MoveForwardingReferenceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *CallMove = Result.Nodes.getNodeAs("call-move"); + const auto *UnresolvedLookup = + Result.Nodes.getNodeAs("lookup"); + const auto *ParmVar = Result.Nodes.getNodeAs("parm-var"); + const auto *TypeParmDecl = + Result.Nodes.getNodeAs("type-parm-decl"); + + // Get the FunctionDecl and FunctionTemplateDecl containing the function + // parameter. + const auto *FuncForParam = dyn_cast(ParmVar->getDeclContext()); + if (!FuncForParam) + return; + const FunctionTemplateDecl *FuncTemplate = + FuncForParam->getDescribedFunctionTemplate(); + if (!FuncTemplate) + return; + + // Check that the template type parameter belongs to the same function + // template as the function parameter of that type. (This implies that type + // deduction will happen on the type.) + const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); + if (!std::count(Params->begin(), Params->end(), TypeParmDecl)) + return; + + auto Diag = diag(CallMove->getExprLoc(), + "forwarding reference passed to std::move(), which may " + "unexpectedly cause lvalues to be moved; use " + "std::forward() instead"); + + replaceMoveWithForward(UnresolvedLookup, ParmVar, TypeParmDecl, Diag, + *Result.Context); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MoveForwardingReferenceCheck.h b/clang-tidy/misc/MoveForwardingReferenceCheck.h new file mode 100644 index 000000000..2e6ec3634 --- /dev/null +++ b/clang-tidy/misc/MoveForwardingReferenceCheck.h @@ -0,0 +1,49 @@ +//===--- MoveForwardingReferenceCheck.h - clang-tidy ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVEFORWARDINGREFERENCECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVEFORWARDINGREFERENCECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check warns if std::move is applied to a forwarding reference (i.e. an +/// rvalue reference of a function template argument type). +/// +/// If a developer is unaware of the special rules for template argument +/// deduction on forwarding references, it will seem reasonable to apply +/// std::move to the forwarding reference, in the same way that this would be +/// done for a "normal" rvalue reference. +/// +/// This has a consequence that is usually unwanted and possibly surprising: if +/// the function that takes the forwarding reference as its parameter is called +/// with an lvalue, that lvalue will be moved from (and hence placed into an +/// indeterminate state) even though no std::move was applied to the lvalue at +/// the call site. +// +/// The check suggests replacing the std::move with a std::forward. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-move-forwarding-reference.html +class MoveForwardingReferenceCheck : public ClangTidyCheck { +public: + MoveForwardingReferenceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVEFORWARDINGREFERENCECHECK_H diff --git a/clang-tidy/misc/MultipleStatementMacroCheck.cpp b/clang-tidy/misc/MultipleStatementMacroCheck.cpp new file mode 100644 index 000000000..9d485fd21 --- /dev/null +++ b/clang-tidy/misc/MultipleStatementMacroCheck.cpp @@ -0,0 +1,106 @@ +//===--- MultipleStatementMacroCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MultipleStatementMacroCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER(Expr, isInMacro) { return Node.getLocStart().isMacroID(); } + +/// \brief Find the next statement after `S`. +const Stmt *nextStmt(const MatchFinder::MatchResult &Result, const Stmt *S) { + auto Parents = Result.Context->getParents(*S); + if (Parents.empty()) + return nullptr; + const auto *Parent = Parents[0].get(); + if (!Parent) + return nullptr; + const Stmt *Prev = nullptr; + for (const Stmt *Child : Parent->children()) { + if (Prev == S) + return Child; + Prev = Child; + } + return nextStmt(Result, Parent); +} + +using ExpansionRanges = std::vector>; + +/// \bried Get all the macro expansion ranges related to `Loc`. +/// +/// The result is ordered from most inner to most outer. +ExpansionRanges getExpansionRanges(SourceLocation Loc, + const MatchFinder::MatchResult &Result) { + ExpansionRanges Locs; + while (Loc.isMacroID()) { + Locs.push_back(Result.SourceManager->getImmediateExpansionRange(Loc)); + Loc = Locs.back().first; + } + return Locs; +} + +} // namespace + +void MultipleStatementMacroCheck::registerMatchers(MatchFinder *Finder) { + const auto Inner = expr(isInMacro(), unless(compoundStmt())).bind("inner"); + Finder->addMatcher( + stmt(anyOf(ifStmt(hasThen(Inner)), ifStmt(hasElse(Inner)).bind("else"), + whileStmt(hasBody(Inner)), forStmt(hasBody(Inner)))) + .bind("outer"), + this); +} + +void MultipleStatementMacroCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Inner = Result.Nodes.getNodeAs("inner"); + const auto *Outer = Result.Nodes.getNodeAs("outer"); + const auto *Next = nextStmt(Result, Outer); + if (!Next) + return; + + SourceLocation OuterLoc = Outer->getLocStart(); + if (Result.Nodes.getNodeAs("else")) + OuterLoc = cast(Outer)->getElseLoc(); + + auto InnerRanges = getExpansionRanges(Inner->getLocStart(), Result); + auto OuterRanges = getExpansionRanges(OuterLoc, Result); + auto NextRanges = getExpansionRanges(Next->getLocStart(), Result); + + // Remove all the common ranges, starting from the top (the last ones in the + // list). + while (!InnerRanges.empty() && !OuterRanges.empty() && !NextRanges.empty() && + InnerRanges.back() == OuterRanges.back() && + InnerRanges.back() == NextRanges.back()) { + InnerRanges.pop_back(); + OuterRanges.pop_back(); + NextRanges.pop_back(); + } + + // Inner and Next must have at least one more macro that Outer doesn't have, + // and that range must be common to both. + if (InnerRanges.empty() || NextRanges.empty() || + InnerRanges.back() != NextRanges.back()) + return; + + diag(InnerRanges.back().first, "multiple statement macro used without " + "braces; some statements will be " + "unconditionally executed"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/MultipleStatementMacroCheck.h b/clang-tidy/misc/MultipleStatementMacroCheck.h new file mode 100644 index 000000000..77a6b27dc --- /dev/null +++ b/clang-tidy/misc/MultipleStatementMacroCheck.h @@ -0,0 +1,37 @@ +//===--- MultipleStatementMacroCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MULTIPLE_STATEMENT_MACRO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MULTIPLE_STATEMENT_MACRO_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect multiple statement macros that are used in unbraced conditionals. +/// Only the first statement of the macro will be inside the conditional and the +/// other ones will be executed unconditionally. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-multiple-statement-macro.html +class MultipleStatementMacroCheck : public ClangTidyCheck { +public: + MultipleStatementMacroCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MULTIPLE_STATEMENT_MACRO_H diff --git a/clang-tidy/misc/NewDeleteOverloadsCheck.cpp b/clang-tidy/misc/NewDeleteOverloadsCheck.cpp new file mode 100644 index 000000000..5e2911957 --- /dev/null +++ b/clang-tidy/misc/NewDeleteOverloadsCheck.cpp @@ -0,0 +1,213 @@ +//===--- NewDeleteOverloadsCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NewDeleteOverloadsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER(FunctionDecl, isPlacementOverload) { + bool New; + switch (Node.getOverloadedOperator()) { + default: + return false; + case OO_New: + case OO_Array_New: + New = true; + break; + case OO_Delete: + case OO_Array_Delete: + New = false; + break; + } + + // Variadic functions are always placement functions. + if (Node.isVariadic()) + return true; + + // Placement new is easy: it always has more than one parameter (the first + // parameter is always the size). If it's an overload of delete or delete[] + // that has only one parameter, it's never a placement delete. + if (New) + return Node.getNumParams() > 1; + if (Node.getNumParams() == 1) + return false; + + // Placement delete is a little more challenging. They always have more than + // one parameter with the first parameter being a pointer. However, the + // second parameter can be a size_t for sized deallocation, and that is never + // a placement delete operator. + if (Node.getNumParams() <= 1 || Node.getNumParams() > 2) + return true; + + const auto *FPT = Node.getType()->castAs(); + ASTContext &Ctx = Node.getASTContext(); + if (Ctx.getLangOpts().SizedDeallocation && + Ctx.hasSameType(FPT->getParamType(1), Ctx.getSizeType())) + return false; + + return true; +} + +OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) { + switch (FD->getOverloadedOperator()) { + default: + break; + case OO_New: + return OO_Delete; + case OO_Delete: + return OO_New; + case OO_Array_New: + return OO_Array_Delete; + case OO_Array_Delete: + return OO_Array_New; + } + llvm_unreachable("Not an overloaded allocation operator"); +} + +const char *getOperatorName(OverloadedOperatorKind K) { + switch (K) { + default: + break; + case OO_New: + return "operator new"; + case OO_Delete: + return "operator delete"; + case OO_Array_New: + return "operator new[]"; + case OO_Array_Delete: + return "operator delete[]"; + } + llvm_unreachable("Not an overloaded allocation operator"); +} + +bool areCorrespondingOverloads(const FunctionDecl *LHS, + const FunctionDecl *RHS) { + return RHS->getOverloadedOperator() == getCorrespondingOverload(LHS); +} + +bool hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, + const CXXRecordDecl *RD = nullptr) { + if (RD) { + // Check the methods in the given class and accessible to derived classes. + for (const auto *BMD : RD->methods()) + if (BMD->isOverloadedOperator() && BMD->getAccess() != AS_private && + areCorrespondingOverloads(MD, BMD)) + return true; + } else { + // Get the parent class of the method; we do not need to care about checking + // the methods in this class as the caller has already done that by looking + // at the declaration contexts. + RD = MD->getParent(); + } + + for (const auto &BS : RD->bases()) { + // We can't say much about a dependent base class, but to avoid false + // positives assume it can have a corresponding overload. + if (BS.getType()->isDependentType()) + return true; + if (const auto *BaseRD = BS.getType()->getAsCXXRecordDecl()) + if (hasCorrespondingOverloadInBaseClass(MD, BaseRD)) + return true; + } + + return false; +} + +} // anonymous namespace + +void NewDeleteOverloadsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Match all operator new and operator delete overloads (including the array + // forms). Do not match implicit operators, placement operators, or + // deleted/private operators. + // + // Technically, trivially-defined operator delete seems like a reasonable + // thing to also skip. e.g., void operator delete(void *) {} + // However, I think it's more reasonable to warn in this case as the user + // should really be writing that as a deleted function. + Finder->addMatcher( + functionDecl(unless(anyOf(isImplicit(), isPlacementOverload(), + isDeleted(), cxxMethodDecl(isPrivate()))), + anyOf(hasOverloadedOperatorName("new"), + hasOverloadedOperatorName("new[]"), + hasOverloadedOperatorName("delete"), + hasOverloadedOperatorName("delete[]"))) + .bind("func"), + this); +} + +void NewDeleteOverloadsCheck::check(const MatchFinder::MatchResult &Result) { + // Add any matches we locate to the list of things to be checked at the + // end of the translation unit. + const auto *FD = Result.Nodes.getNodeAs("func"); + const CXXRecordDecl *RD = nullptr; + if (const auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + Overloads[RD].push_back(FD); +} + +void NewDeleteOverloadsCheck::onEndOfTranslationUnit() { + // Walk over the list of declarations we've found to see if there is a + // corresponding overload at the same declaration context or within a base + // class. If there is not, add the element to the list of declarations to + // diagnose. + SmallVector Diagnose; + for (const auto &RP : Overloads) { + // We don't care about the CXXRecordDecl key in the map; we use it as a way + // to shard the overloads by declaration context to reduce the algorithmic + // complexity when searching for corresponding free store functions. + for (const auto *Overload : RP.second) { + const auto *Match = + std::find_if(RP.second.begin(), RP.second.end(), + [&Overload](const FunctionDecl *FD) { + if (FD == Overload) + return false; + // If the declaration contexts don't match, we don't + // need to check any further. + if (FD->getDeclContext() != Overload->getDeclContext()) + return false; + + // Since the declaration contexts match, see whether + // the current element is the corresponding operator. + if (!areCorrespondingOverloads(Overload, FD)) + return false; + + return true; + }); + + if (Match == RP.second.end()) { + // Check to see if there is a corresponding overload in a base class + // context. If there isn't, or if the overload is not a class member + // function, then we should diagnose. + const auto *MD = dyn_cast(Overload); + if (!MD || !hasCorrespondingOverloadInBaseClass(MD)) + Diagnose.push_back(Overload); + } + } + } + + for (const auto *FD : Diagnose) + diag(FD->getLocation(), "declaration of %0 has no matching declaration " + "of '%1' at the same scope") + << FD << getOperatorName(getCorrespondingOverload(FD)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/NewDeleteOverloadsCheck.h b/clang-tidy/misc/NewDeleteOverloadsCheck.h new file mode 100644 index 000000000..3e99892b3 --- /dev/null +++ b/clang-tidy/misc/NewDeleteOverloadsCheck.h @@ -0,0 +1,38 @@ +//===--- NewDeleteOverloadsCheck.h - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallVector.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +class NewDeleteOverloadsCheck : public ClangTidyCheck { + std::map> + Overloads; + +public: + NewDeleteOverloadsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NEWDELETEOVERLOADS_H diff --git a/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp b/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp new file mode 100644 index 000000000..f145db824 --- /dev/null +++ b/clang-tidy/misc/NoexceptMoveConstructorCheck.cpp @@ -0,0 +1,73 @@ +//===--- NoexceptMoveConstructorCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NoexceptMoveConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void NoexceptMoveConstructorCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxMethodDecl(anyOf(cxxConstructorDecl(), hasOverloadedOperatorName("=")), + unless(isImplicit()), unless(isDeleted())) + .bind("decl"), + this); +} + +void NoexceptMoveConstructorCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { + StringRef MethodType = "assignment operator"; + if (const auto *Ctor = dyn_cast(Decl)) { + if (!Ctor->isMoveConstructor()) + return; + MethodType = "constructor"; + } else if (!Decl->isMoveAssignmentOperator()) { + return; + } + + const auto *ProtoType = Decl->getType()->getAs(); + switch (ProtoType->getNoexceptSpec(*Result.Context)) { + case FunctionProtoType::NR_NoNoexcept: + diag(Decl->getLocation(), "move %0s should be marked noexcept") + << MethodType; + // FIXME: Add a fixit. + break; + case FunctionProtoType::NR_Throw: + // Don't complain about nothrow(false), but complain on nothrow(expr) + // where expr evaluates to false. + if (const Expr *E = ProtoType->getNoexceptExpr()) { + if (isa(E)) + break; + diag(E->getExprLoc(), + "noexcept specifier on the move %0 evaluates to 'false'") + << MethodType; + } + break; + case FunctionProtoType::NR_Nothrow: + case FunctionProtoType::NR_Dependent: + case FunctionProtoType::NR_BadNoexcept: + break; + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/NoexceptMoveConstructorCheck.h b/clang-tidy/misc/NoexceptMoveConstructorCheck.h new file mode 100644 index 000000000..e6a0ef928 --- /dev/null +++ b/clang-tidy/misc/NoexceptMoveConstructorCheck.h @@ -0,0 +1,38 @@ +//===--- NoexceptMoveConstructorCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check flags user-defined move constructors and assignment operators not +/// marked with `noexcept` or marked with `noexcept(expr)` where `expr` +/// evaluates to `false` (but is not a `false` literal itself). +/// +/// Move constructors of all the types used with STL containers, for example, +/// need to be declared `noexcept`. Otherwise STL will choose copy constructors +/// instead. The same is valid for move assignment operations. +class NoexceptMoveConstructorCheck : public ClangTidyCheck { +public: + NoexceptMoveConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NOEXCEPTMOVECONSTRUCTORCHECK_H diff --git a/clang-tidy/misc/NonCopyableObjects.cpp b/clang-tidy/misc/NonCopyableObjects.cpp new file mode 100644 index 000000000..de152754e --- /dev/null +++ b/clang-tidy/misc/NonCopyableObjects.cpp @@ -0,0 +1,74 @@ +//===--- NonCopyableObjects.cpp - clang-tidy-------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonCopyableObjects.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void NonCopyableObjectsCheck::registerMatchers(MatchFinder *Finder) { + // There are two ways to get into trouble with objects like FILE *: + // dereferencing the pointer type to be a non-pointer type, and declaring + // the type as a non-pointer type in the first place. While the declaration + // itself could technically be well-formed in the case where the type is not + // an opaque type, it's highly suspicious behavior. + // + // POSIX types are a bit different in that it's reasonable to declare a + // non-pointer variable or data member of the type, but it is not reasonable + // to dereference a pointer to the type, or declare a parameter of non-pointer + // type. + // FIXME: it would be good to make a list that is also user-configurable so + // that users can add their own elements to the list. However, it may require + // some extra thought since POSIX types and FILE types are usable in different + // ways. + + auto BadFILEType = hasType( + namedDecl(hasAnyName("::FILE", "FILE", "std::FILE")).bind("type_decl")); + auto BadPOSIXType = + hasType(namedDecl(hasAnyName("::pthread_cond_t", "::pthread_mutex_t", + "pthread_cond_t", "pthread_mutex_t")) + .bind("type_decl")); + auto BadEitherType = anyOf(BadFILEType, BadPOSIXType); + + Finder->addMatcher( + namedDecl(anyOf(varDecl(BadFILEType), fieldDecl(BadFILEType))) + .bind("decl"), + this); + Finder->addMatcher(parmVarDecl(BadPOSIXType).bind("decl"), this); + Finder->addMatcher( + expr(unaryOperator(hasOperatorName("*"), BadEitherType)).bind("expr"), + this); +} + +void NonCopyableObjectsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("decl"); + const auto *BD = Result.Nodes.getNodeAs("type_decl"); + const auto *E = Result.Nodes.getNodeAs("expr"); + + if (D && BD) + diag(D->getLocation(), "%0 declared as type '%1', which is unsafe to copy" + "; did you mean '%1 *'?") + << D << BD->getName(); + else if (E) + diag(E->getExprLoc(), + "expression has opaque data structure type %0; type should only be " + "used as a pointer and not dereferenced") + << BD; +} + +} // namespace misc +} // namespace tidy +} // namespace clang + diff --git a/clang-tidy/misc/NonCopyableObjects.h b/clang-tidy/misc/NonCopyableObjects.h new file mode 100644 index 000000000..38a45fd55 --- /dev/null +++ b/clang-tidy/misc/NonCopyableObjects.h @@ -0,0 +1,33 @@ +//===--- NonCopyableObjects.h - clang-tidy-----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check flags dereferences and non-pointer declarations of objects that +/// are not meant to be passed by value, such as C FILE objects. +class NonCopyableObjectsCheck : public ClangTidyCheck { +public: + NonCopyableObjectsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_NONCOPYABLEOBJECTS_H diff --git a/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tidy/misc/RedundantExpressionCheck.cpp new file mode 100644 index 000000000..aeaeb63c8 --- /dev/null +++ b/clang-tidy/misc/RedundantExpressionCheck.cpp @@ -0,0 +1,740 @@ +//===--- RedundantExpressionCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantExpressionCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include +#include +#include + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +using llvm::APSInt; +} // namespace + +static const char KnownBannedMacroNames[] = + "EAGAIN;EWOULDBLOCK;SIGCLD;SIGCHLD;"; + +static bool incrementWithoutOverflow(const APSInt &Value, APSInt &Result) { + Result = Value; + ++Result; + return Value < Result; +} + +static bool areEquivalentNameSpecifier(const NestedNameSpecifier *Left, + const NestedNameSpecifier *Right) { + llvm::FoldingSetNodeID LeftID, RightID; + Left->Profile(LeftID); + Right->Profile(RightID); + return LeftID == RightID; +} + +static bool areEquivalentExpr(const Expr *Left, const Expr *Right) { + if (!Left || !Right) + return !Left && !Right; + + Left = Left->IgnoreParens(); + Right = Right->IgnoreParens(); + + // Compare classes. + if (Left->getStmtClass() != Right->getStmtClass()) + return false; + + // Compare children. + Expr::const_child_iterator LeftIter = Left->child_begin(); + Expr::const_child_iterator RightIter = Right->child_begin(); + while (LeftIter != Left->child_end() && RightIter != Right->child_end()) { + if (!areEquivalentExpr(dyn_cast(*LeftIter), + dyn_cast(*RightIter))) + return false; + ++LeftIter; + ++RightIter; + } + if (LeftIter != Left->child_end() || RightIter != Right->child_end()) + return false; + + // Perform extra checks. + switch (Left->getStmtClass()) { + default: + return false; + + case Stmt::CharacterLiteralClass: + return cast(Left)->getValue() == + cast(Right)->getValue(); + case Stmt::IntegerLiteralClass: { + llvm::APInt LeftLit = cast(Left)->getValue(); + llvm::APInt RightLit = cast(Right)->getValue(); + return LeftLit.getBitWidth() == RightLit.getBitWidth() && + LeftLit == RightLit; + } + case Stmt::FloatingLiteralClass: + return cast(Left)->getValue().bitwiseIsEqual( + cast(Right)->getValue()); + case Stmt::StringLiteralClass: + return cast(Left)->getBytes() == + cast(Right)->getBytes(); + + case Stmt::DependentScopeDeclRefExprClass: + if (cast(Left)->getDeclName() != + cast(Right)->getDeclName()) + return false; + return areEquivalentNameSpecifier( + cast(Left)->getQualifier(), + cast(Right)->getQualifier()); + case Stmt::DeclRefExprClass: + return cast(Left)->getDecl() == + cast(Right)->getDecl(); + case Stmt::MemberExprClass: + return cast(Left)->getMemberDecl() == + cast(Right)->getMemberDecl(); + + case Stmt::CStyleCastExprClass: + return cast(Left)->getTypeAsWritten() == + cast(Right)->getTypeAsWritten(); + + case Stmt::CallExprClass: + case Stmt::ImplicitCastExprClass: + case Stmt::ArraySubscriptExprClass: + return true; + + case Stmt::UnaryOperatorClass: + if (cast(Left)->isIncrementDecrementOp()) + return false; + return cast(Left)->getOpcode() == + cast(Right)->getOpcode(); + case Stmt::BinaryOperatorClass: + return cast(Left)->getOpcode() == + cast(Right)->getOpcode(); + } +} + +// For a given expression 'x', returns whether the ranges covered by the +// relational operators are equivalent (i.e. x <= 4 is equivalent to x < 5). +static bool areEquivalentRanges(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + assert(APSInt::compareValues(ValueLHS, ValueRHS) <= 0 && + "Values must be ordered"); + // Handle the case where constants are the same: x <= 4 <==> x <= 4. + if (APSInt::compareValues(ValueLHS, ValueRHS) == 0) + return OpcodeLHS == OpcodeRHS; + + // Handle the case where constants are off by one: x <= 4 <==> x < 5. + APSInt ValueLHS_plus1; + return ((OpcodeLHS == BO_LE && OpcodeRHS == BO_LT) || + (OpcodeLHS == BO_GT && OpcodeRHS == BO_GE)) && + incrementWithoutOverflow(ValueLHS, ValueLHS_plus1) && + APSInt::compareValues(ValueLHS_plus1, ValueRHS) == 0; +} + +// For a given expression 'x', returns whether the ranges covered by the +// relational operators are fully disjoint (i.e. x < 4 and x > 7). +static bool areExclusiveRanges(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + assert(APSInt::compareValues(ValueLHS, ValueRHS) <= 0 && + "Values must be ordered"); + + // Handle cases where the constants are the same. + if (APSInt::compareValues(ValueLHS, ValueRHS) == 0) { + switch (OpcodeLHS) { + case BO_EQ: + return OpcodeRHS == BO_NE || OpcodeRHS == BO_GT || OpcodeRHS == BO_LT; + case BO_NE: + return OpcodeRHS == BO_EQ; + case BO_LE: + return OpcodeRHS == BO_GT; + case BO_GE: + return OpcodeRHS == BO_LT; + case BO_LT: + return OpcodeRHS == BO_EQ || OpcodeRHS == BO_GT || OpcodeRHS == BO_GE; + case BO_GT: + return OpcodeRHS == BO_EQ || OpcodeRHS == BO_LT || OpcodeRHS == BO_LE; + default: + return false; + } + } + + // Handle cases where the constants are different. + if ((OpcodeLHS == BO_EQ || OpcodeLHS == BO_LT || OpcodeLHS == BO_LE) && + (OpcodeRHS == BO_EQ || OpcodeRHS == BO_GT || OpcodeRHS == BO_GE)) + return true; + + // Handle the case where constants are off by one: x > 5 && x < 6. + APSInt ValueLHS_plus1; + if (OpcodeLHS == BO_GT && OpcodeRHS == BO_LT && + incrementWithoutOverflow(ValueLHS, ValueLHS_plus1) && + APSInt::compareValues(ValueLHS_plus1, ValueRHS) == 0) + return true; + + return false; +} + +// Returns whether the ranges covered by the union of both relational +// expressions covers the whole domain (i.e. x < 10 and x > 0). +static bool rangesFullyCoverDomain(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + assert(APSInt::compareValues(ValueLHS, ValueRHS) <= 0 && + "Values must be ordered"); + + // Handle cases where the constants are the same: x < 5 || x >= 5. + if (APSInt::compareValues(ValueLHS, ValueRHS) == 0) { + switch (OpcodeLHS) { + case BO_EQ: + return OpcodeRHS == BO_NE; + case BO_NE: + return OpcodeRHS == BO_EQ; + case BO_LE: + return OpcodeRHS == BO_GT || OpcodeRHS == BO_GE; + case BO_LT: + return OpcodeRHS == BO_GE; + case BO_GE: + return OpcodeRHS == BO_LT || OpcodeRHS == BO_LE; + case BO_GT: + return OpcodeRHS == BO_LE; + default: + return false; + } + } + + // Handle the case where constants are off by one: x <= 4 || x >= 5. + APSInt ValueLHS_plus1; + if (OpcodeLHS == BO_LE && OpcodeRHS == BO_GE && + incrementWithoutOverflow(ValueLHS, ValueLHS_plus1) && + APSInt::compareValues(ValueLHS_plus1, ValueRHS) == 0) + return true; + + // Handle cases where the constants are different: x > 4 || x <= 7. + if ((OpcodeLHS == BO_GT || OpcodeLHS == BO_GE) && + (OpcodeRHS == BO_LT || OpcodeRHS == BO_LE)) + return true; + + return false; +} + +static bool rangeSubsumesRange(BinaryOperatorKind OpcodeLHS, + const APSInt &ValueLHS, + BinaryOperatorKind OpcodeRHS, + const APSInt &ValueRHS) { + int Comparison = APSInt::compareValues(ValueLHS, ValueRHS); + switch (OpcodeLHS) { + case BO_EQ: + return OpcodeRHS == BO_EQ && Comparison == 0; + case BO_NE: + return (OpcodeRHS == BO_NE && Comparison == 0) || + (OpcodeRHS == BO_EQ && Comparison != 0) || + (OpcodeRHS == BO_LT && Comparison >= 0) || + (OpcodeRHS == BO_LE && Comparison > 0) || + (OpcodeRHS == BO_GT && Comparison <= 0) || + (OpcodeRHS == BO_GE && Comparison < 0); + + case BO_LT: + return ((OpcodeRHS == BO_LT && Comparison >= 0) || + (OpcodeRHS == BO_LE && Comparison > 0) || + (OpcodeRHS == BO_EQ && Comparison > 0)); + case BO_GT: + return ((OpcodeRHS == BO_GT && Comparison <= 0) || + (OpcodeRHS == BO_GE && Comparison < 0) || + (OpcodeRHS == BO_EQ && Comparison < 0)); + case BO_LE: + return (OpcodeRHS == BO_LT || OpcodeRHS == BO_LE || OpcodeRHS == BO_EQ) && + Comparison >= 0; + case BO_GE: + return (OpcodeRHS == BO_GT || OpcodeRHS == BO_GE || OpcodeRHS == BO_EQ) && + Comparison <= 0; + default: + return false; + } +} + +static void canonicalNegateExpr(BinaryOperatorKind &Opcode, APSInt &Value) { + if (Opcode == BO_Sub) { + Opcode = BO_Add; + Value = -Value; + } +} + +AST_MATCHER(Expr, isIntegerConstantExpr) { + if (Node.isInstantiationDependent()) + return false; + return Node.isIntegerConstantExpr(Finder->getASTContext()); +} + +// Returns a matcher for integer constant expression. +static ast_matchers::internal::Matcher +matchIntegerConstantExpr(StringRef Id) { + std::string CstId = (Id + "-const").str(); + return expr(isIntegerConstantExpr()).bind(CstId); +} + +// Retrieve the integer value matched by 'matchIntegerConstantExpr' with name +// 'Id' and store it into 'Value'. +static bool retrieveIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, APSInt &Value) { + std::string CstId = (Id + "-const").str(); + const auto *CstExpr = Result.Nodes.getNodeAs(CstId); + return CstExpr && CstExpr->isIntegerConstantExpr(Value, *Result.Context); +} + +// Returns a matcher for a symbolic expression (any expression except ingeter +// constant expression). +static ast_matchers::internal::Matcher matchSymbolicExpr(StringRef Id) { + std::string SymId = (Id + "-sym").str(); + return ignoringParenImpCasts( + expr(unless(isIntegerConstantExpr())).bind(SymId)); +} + +// Retrieve the expression matched by 'matchSymbolicExpr' with name 'Id' and +// store it into 'SymExpr'. +static bool retrieveSymbolicExpr(const MatchFinder::MatchResult &Result, + StringRef Id, const Expr *&SymExpr) { + std::string SymId = (Id + "-sym").str(); + if (const auto *Node = Result.Nodes.getNodeAs(SymId)) { + SymExpr = Node; + return true; + } + return false; +} + +// Match a binary operator between a symbolic expression and an integer constant +// expression. +static ast_matchers::internal::Matcher +matchBinOpIntegerConstantExpr(StringRef Id) { + const auto BinOpCstExpr = + expr( + anyOf(binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|"), + hasOperatorName("&")), + hasEitherOperand(matchSymbolicExpr(Id)), + hasEitherOperand(matchIntegerConstantExpr(Id))), + binaryOperator(hasOperatorName("-"), + hasLHS(matchSymbolicExpr(Id)), + hasRHS(matchIntegerConstantExpr(Id))))) + .bind(Id); + return ignoringParenImpCasts(BinOpCstExpr); +} + +// Retrieve sub-expressions matched by 'matchBinOpIntegerConstantExpr' with +// name 'Id'. +static bool +retrieveBinOpIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, BinaryOperatorKind &Opcode, + const Expr *&Symbol, APSInt &Value) { + if (const auto *BinExpr = Result.Nodes.getNodeAs(Id)) { + Opcode = BinExpr->getOpcode(); + return retrieveSymbolicExpr(Result, Id, Symbol) && + retrieveIntegerConstantExpr(Result, Id, Value); + } + return false; +} + +// Matches relational expression: 'Expr k' (i.e. x < 2, x != 3, 12 <= x). +static ast_matchers::internal::Matcher +matchRelationalIntegerConstantExpr(StringRef Id) { + std::string CastId = (Id + "-cast").str(); + std::string SwapId = (Id + "-swap").str(); + std::string NegateId = (Id + "-negate").str(); + + const auto RelationalExpr = ignoringParenImpCasts(binaryOperator( + isComparisonOperator(), expr().bind(Id), + anyOf(allOf(hasLHS(matchSymbolicExpr(Id)), + hasRHS(matchIntegerConstantExpr(Id))), + allOf(hasLHS(matchIntegerConstantExpr(Id)), + hasRHS(matchSymbolicExpr(Id)), expr().bind(SwapId))))); + + // A cast can be matched as a comparator to zero. (i.e. if (x) is equivalent + // to if (x != 0)). + const auto CastExpr = + implicitCastExpr(hasCastKind(CK_IntegralToBoolean), + hasSourceExpression(matchSymbolicExpr(Id))) + .bind(CastId); + + const auto NegateRelationalExpr = + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(anyOf(CastExpr, RelationalExpr))) + .bind(NegateId); + + const auto NegateNegateRelationalExpr = + unaryOperator(hasOperatorName("!"), + hasUnaryOperand(unaryOperator( + hasOperatorName("!"), + hasUnaryOperand(anyOf(CastExpr, RelationalExpr))))); + + return anyOf(RelationalExpr, CastExpr, NegateRelationalExpr, + NegateNegateRelationalExpr); +} + +// Retrieve sub-expressions matched by 'matchRelationalIntegerConstantExpr' with +// name 'Id'. +static bool +retrieveRelationalIntegerConstantExpr(const MatchFinder::MatchResult &Result, + StringRef Id, const Expr *&OperandExpr, + BinaryOperatorKind &Opcode, + const Expr *&Symbol, APSInt &Value) { + std::string CastId = (Id + "-cast").str(); + std::string SwapId = (Id + "-swap").str(); + std::string NegateId = (Id + "-negate").str(); + + if (const auto *Bin = Result.Nodes.getNodeAs(Id)) { + // Operand received with explicit comparator. + Opcode = Bin->getOpcode(); + OperandExpr = Bin; + if (!retrieveIntegerConstantExpr(Result, Id, Value)) + return false; + } else if (const auto *Cast = Result.Nodes.getNodeAs(CastId)) { + // Operand received with implicit comparator (cast). + Opcode = BO_NE; + OperandExpr = Cast; + Value = APSInt(32, false); + } else { + return false; + } + + if (!retrieveSymbolicExpr(Result, Id, Symbol)) + return false; + + if (Result.Nodes.getNodeAs(SwapId)) + Opcode = BinaryOperator::reverseComparisonOp(Opcode); + if (Result.Nodes.getNodeAs(NegateId)) + Opcode = BinaryOperator::negateComparisonOp(Opcode); + + return true; +} + +AST_MATCHER(BinaryOperator, operandsAreEquivalent) { + return areEquivalentExpr(Node.getLHS(), Node.getRHS()); +} + +AST_MATCHER(ConditionalOperator, expressionsAreEquivalent) { + return areEquivalentExpr(Node.getTrueExpr(), Node.getFalseExpr()); +} + +AST_MATCHER(CallExpr, parametersAreEquivalent) { + return Node.getNumArgs() == 2 && + areEquivalentExpr(Node.getArg(0), Node.getArg(1)); +} + +AST_MATCHER(BinaryOperator, binaryOperatorIsInMacro) { + return Node.getOperatorLoc().isMacroID(); +} + +AST_MATCHER(ConditionalOperator, conditionalOperatorIsInMacro) { + return Node.getQuestionLoc().isMacroID() || Node.getColonLoc().isMacroID(); +} + +AST_MATCHER(Expr, isMacro) { return Node.getExprLoc().isMacroID(); } + +AST_MATCHER_P(Expr, expandedByMacro, std::set, Names) { + const SourceManager &SM = Finder->getASTContext().getSourceManager(); + const LangOptions &LO = Finder->getASTContext().getLangOpts(); + SourceLocation Loc = Node.getExprLoc(); + while (Loc.isMacroID()) { + std::string MacroName = Lexer::getImmediateMacroName(Loc, SM, LO); + if (Names.find(MacroName) != Names.end()) + return true; + Loc = SM.getImmediateMacroCallerLoc(Loc); + } + return false; +} + +void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) { + const auto AnyLiteralExpr = ignoringParenImpCasts( + anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral())); + + std::vector MacroNames = + utils::options::parseStringList(KnownBannedMacroNames); + std::set Names(MacroNames.begin(), MacroNames.end()); + + const auto BannedIntegerLiteral = integerLiteral(expandedByMacro(Names)); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("-"), hasOperatorName("/"), + hasOperatorName("%"), hasOperatorName("|"), + hasOperatorName("&"), hasOperatorName("^"), + matchers::isComparisonOperator(), + hasOperatorName("&&"), hasOperatorName("||"), + hasOperatorName("=")), + operandsAreEquivalent(), + // Filter noisy false positives. + unless(isInTemplateInstantiation()), + unless(binaryOperatorIsInMacro()), + unless(hasType(realFloatingPointType())), + unless(hasEitherOperand(hasType(realFloatingPointType()))), + unless(hasLHS(AnyLiteralExpr)), + unless(hasDescendant(BannedIntegerLiteral))) + .bind("binary"), + this); + + Finder->addMatcher( + conditionalOperator(expressionsAreEquivalent(), + // Filter noisy false positives. + unless(conditionalOperatorIsInMacro()), + unless(hasTrueExpression(AnyLiteralExpr)), + unless(isInTemplateInstantiation())) + .bind("cond"), + this); + + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf( + hasOverloadedOperatorName("-"), hasOverloadedOperatorName("/"), + hasOverloadedOperatorName("%"), hasOverloadedOperatorName("|"), + hasOverloadedOperatorName("&"), hasOverloadedOperatorName("^"), + hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!="), + hasOverloadedOperatorName("<"), hasOverloadedOperatorName("<="), + hasOverloadedOperatorName(">"), hasOverloadedOperatorName(">="), + hasOverloadedOperatorName("&&"), hasOverloadedOperatorName("||"), + hasOverloadedOperatorName("=")), + parametersAreEquivalent(), + // Filter noisy false positives. + unless(isMacro()), unless(isInTemplateInstantiation())) + .bind("call"), + this); + + // Match common expressions and apply more checks to find redundant + // sub-expressions. + // a) Expr K1 == K2 + // b) Expr K1 == Expr + // c) Expr K1 == Expr K2 + // see: 'checkArithmeticExpr' and 'checkBitwiseExpr' + const auto BinOpCstLeft = matchBinOpIntegerConstantExpr("lhs"); + const auto BinOpCstRight = matchBinOpIntegerConstantExpr("rhs"); + const auto CstRight = matchIntegerConstantExpr("rhs"); + const auto SymRight = matchSymbolicExpr("rhs"); + + // Match expressions like: x 0xFF == 0xF00. + Finder->addMatcher(binaryOperator(isComparisonOperator(), + hasEitherOperand(BinOpCstLeft), + hasEitherOperand(CstRight)) + .bind("binop-const-compare-to-const"), + this); + + // Match expressions like: x 0xFF == x. + Finder->addMatcher( + binaryOperator(isComparisonOperator(), + anyOf(allOf(hasLHS(BinOpCstLeft), hasRHS(SymRight)), + allOf(hasLHS(SymRight), hasRHS(BinOpCstLeft)))) + .bind("binop-const-compare-to-sym"), + this); + + // Match expressions like: x 10 == x 12. + Finder->addMatcher(binaryOperator(isComparisonOperator(), + hasLHS(BinOpCstLeft), hasRHS(BinOpCstRight), + // Already reported as redundant. + unless(operandsAreEquivalent())) + .bind("binop-const-compare-to-binop-const"), + this); + + // Match relational expressions combined with logical operators and find + // redundant sub-expressions. + // see: 'checkRelationalExpr' + + // Match expressions like: x < 2 && x > 2. + const auto ComparisonLeft = matchRelationalIntegerConstantExpr("lhs"); + const auto ComparisonRight = matchRelationalIntegerConstantExpr("rhs"); + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("||"), hasOperatorName("&&")), + hasLHS(ComparisonLeft), hasRHS(ComparisonRight), + // Already reported as redundant. + unless(operandsAreEquivalent())) + .bind("comparisons-of-symbol-and-const"), + this); +} + +void RedundantExpressionCheck::checkArithmeticExpr( + const MatchFinder::MatchResult &Result) { + APSInt LhsValue, RhsValue; + const Expr *LhsSymbol = nullptr, *RhsSymbol = nullptr; + BinaryOperatorKind LhsOpcode, RhsOpcode; + + if (const auto *ComparisonOperator = Result.Nodes.getNodeAs( + "binop-const-compare-to-sym")) { + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + if (!retrieveBinOpIntegerConstantExpr(Result, "lhs", LhsOpcode, LhsSymbol, + LhsValue) || + !retrieveSymbolicExpr(Result, "rhs", RhsSymbol) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + // Check expressions: x + k == x or x - k == x. + if (LhsOpcode == BO_Add || LhsOpcode == BO_Sub) { + if ((LhsValue != 0 && Opcode == BO_EQ) || + (LhsValue == 0 && Opcode == BO_NE)) + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always false"); + else if ((LhsValue == 0 && Opcode == BO_EQ) || + (LhsValue != 0 && Opcode == BO_NE)) + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always true"); + } + } else if (const auto *ComparisonOperator = + Result.Nodes.getNodeAs( + "binop-const-compare-to-binop-const")) { + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + if (!retrieveBinOpIntegerConstantExpr(Result, "lhs", LhsOpcode, LhsSymbol, + LhsValue) || + !retrieveBinOpIntegerConstantExpr(Result, "rhs", RhsOpcode, RhsSymbol, + RhsValue) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + canonicalNegateExpr(LhsOpcode, LhsValue); + canonicalNegateExpr(RhsOpcode, RhsValue); + + // Check expressions: x + 1 == x + 2 or x + 1 != x + 2. + if (LhsOpcode == BO_Add && RhsOpcode == BO_Add) { + if ((Opcode == BO_EQ && APSInt::compareValues(LhsValue, RhsValue) == 0) || + (Opcode == BO_NE && APSInt::compareValues(LhsValue, RhsValue) != 0)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always true"); + } else if ((Opcode == BO_EQ && + APSInt::compareValues(LhsValue, RhsValue) != 0) || + (Opcode == BO_NE && + APSInt::compareValues(LhsValue, RhsValue) == 0)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always false"); + } + } + } +} + +void RedundantExpressionCheck::checkBitwiseExpr( + const MatchFinder::MatchResult &Result) { + if (const auto *ComparisonOperator = Result.Nodes.getNodeAs( + "binop-const-compare-to-const")) { + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + APSInt LhsValue, RhsValue; + const Expr *LhsSymbol = nullptr; + BinaryOperatorKind LhsOpcode; + if (!retrieveBinOpIntegerConstantExpr(Result, "lhs", LhsOpcode, LhsSymbol, + LhsValue) || + !retrieveIntegerConstantExpr(Result, "rhs", RhsValue)) + return; + + uint64_t LhsConstant = LhsValue.getZExtValue(); + uint64_t RhsConstant = RhsValue.getZExtValue(); + SourceLocation Loc = ComparisonOperator->getOperatorLoc(); + + // Check expression: x & k1 == k2 (i.e. x & 0xFF == 0xF00) + if (LhsOpcode == BO_And && (LhsConstant & RhsConstant) != RhsConstant) { + if (Opcode == BO_EQ) + diag(Loc, "logical expression is always false"); + else if (Opcode == BO_NE) + diag(Loc, "logical expression is always true"); + } + + // Check expression: x | k1 == k2 (i.e. x | 0xFF == 0xF00) + if (LhsOpcode == BO_Or && (LhsConstant | RhsConstant) != RhsConstant) { + if (Opcode == BO_EQ) + diag(Loc, "logical expression is always false"); + else if (Opcode == BO_NE) + diag(Loc, "logical expression is always true"); + } + } +} + +void RedundantExpressionCheck::checkRelationalExpr( + const MatchFinder::MatchResult &Result) { + if (const auto *ComparisonOperator = Result.Nodes.getNodeAs( + "comparisons-of-symbol-and-const")) { + // Matched expressions are: (x k1) (x k2). + BinaryOperatorKind Opcode = ComparisonOperator->getOpcode(); + + const Expr *LhsExpr = nullptr, *RhsExpr = nullptr; + APSInt LhsValue, RhsValue; + const Expr *LhsSymbol = nullptr, *RhsSymbol = nullptr; + BinaryOperatorKind LhsOpcode, RhsOpcode; + if (!retrieveRelationalIntegerConstantExpr( + Result, "lhs", LhsExpr, LhsOpcode, LhsSymbol, LhsValue) || + !retrieveRelationalIntegerConstantExpr( + Result, "rhs", RhsExpr, RhsOpcode, RhsSymbol, RhsValue) || + !areEquivalentExpr(LhsSymbol, RhsSymbol)) + return; + + // Bring to a canonical form: smallest constant must be on the left side. + if (APSInt::compareValues(LhsValue, RhsValue) > 0) { + std::swap(LhsExpr, RhsExpr); + std::swap(LhsValue, RhsValue); + std::swap(LhsSymbol, RhsSymbol); + std::swap(LhsOpcode, RhsOpcode); + } + + if ((Opcode == BO_LAnd || Opcode == BO_LOr) && + areEquivalentRanges(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "equivalent expression on both side of logical operator"); + return; + } + + if (Opcode == BO_LAnd) { + if (areExclusiveRanges(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always false"); + } else if (rangeSubsumesRange(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(LhsExpr->getExprLoc(), "expression is redundant"); + } else if (rangeSubsumesRange(RhsOpcode, RhsValue, LhsOpcode, LhsValue)) { + diag(RhsExpr->getExprLoc(), "expression is redundant"); + } + } + + if (Opcode == BO_LOr) { + if (rangesFullyCoverDomain(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(ComparisonOperator->getOperatorLoc(), + "logical expression is always true"); + } else if (rangeSubsumesRange(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) { + diag(RhsExpr->getExprLoc(), "expression is redundant"); + } else if (rangeSubsumesRange(RhsOpcode, RhsValue, LhsOpcode, LhsValue)) { + diag(LhsExpr->getExprLoc(), "expression is redundant"); + } + } + } +} + +void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *BinOp = Result.Nodes.getNodeAs("binary")) + diag(BinOp->getOperatorLoc(), "both side of operator are equivalent"); + if (const auto *CondOp = Result.Nodes.getNodeAs("cond")) + diag(CondOp->getColonLoc(), "'true' and 'false' expression are equivalent"); + if (const auto *Call = Result.Nodes.getNodeAs("call")) + diag(Call->getOperatorLoc(), + "both side of overloaded operator are equivalent"); + + checkArithmeticExpr(Result); + checkBitwiseExpr(Result); + checkRelationalExpr(Result); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/RedundantExpressionCheck.h b/clang-tidy/misc/RedundantExpressionCheck.h new file mode 100644 index 000000000..59d2c8f95 --- /dev/null +++ b/clang-tidy/misc/RedundantExpressionCheck.h @@ -0,0 +1,40 @@ +//===--- RedundantExpressionCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_REDUNDANT_EXPRESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_REDUNDANT_EXPRESSION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Detect useless or suspicious redundant expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-redundant-expression.html +class RedundantExpressionCheck : public ClangTidyCheck { +public: + RedundantExpressionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkArithmeticExpr(const ast_matchers::MatchFinder::MatchResult &R); + void checkBitwiseExpr(const ast_matchers::MatchFinder::MatchResult &R); + void checkRelationalExpr(const ast_matchers::MatchFinder::MatchResult &R); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_REDUNDANT_EXPRESSION_H diff --git a/clang-tidy/misc/SizeofContainerCheck.cpp b/clang-tidy/misc/SizeofContainerCheck.cpp new file mode 100644 index 000000000..de4e8ad1b --- /dev/null +++ b/clang-tidy/misc/SizeofContainerCheck.cpp @@ -0,0 +1,49 @@ +//===--- SizeofContainerCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SizeofContainerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SizeofContainerCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + expr(unless(isInTemplateInstantiation()), + expr(sizeOfExpr(has(ignoringParenImpCasts( + expr(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl( + matchesName("^(::std::|::string)"), + unless(matchesName("^::std::(bitset|array)$")), + hasMethod(cxxMethodDecl(hasName("size"), isPublic(), + isConst()))))))))))) + .bind("sizeof"), + // Ignore ARRAYSIZE() pattern. + unless(hasAncestor(binaryOperator( + anyOf(hasOperatorName("/"), hasOperatorName("%")), + hasLHS(ignoringParenCasts(sizeOfExpr(expr()))), + hasRHS(ignoringParenCasts(equalsBoundNode("sizeof"))))))), + this); +} + +void SizeofContainerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *SizeOf = + Result.Nodes.getNodeAs("sizeof"); + + auto Diag = + diag(SizeOf->getLocStart(), "sizeof() doesn't return the size of the " + "container; did you mean .size()?"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SizeofContainerCheck.h b/clang-tidy/misc/SizeofContainerCheck.h new file mode 100644 index 000000000..ed13ca56d --- /dev/null +++ b/clang-tidy/misc/SizeofContainerCheck.h @@ -0,0 +1,36 @@ +//===--- SizeofContainerCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find usages of sizeof on expressions of STL container types. Most likely the +/// user wanted to use `.size()` instead. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-container.html +class SizeofContainerCheck : public ClangTidyCheck { +public: + SizeofContainerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_CONTAINER_H diff --git a/clang-tidy/misc/SizeofExpressionCheck.cpp b/clang-tidy/misc/SizeofExpressionCheck.cpp new file mode 100644 index 000000000..5b8e7e763 --- /dev/null +++ b/clang-tidy/misc/SizeofExpressionCheck.cpp @@ -0,0 +1,265 @@ +//===--- SizeofExpressionCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SizeofExpressionCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) { + return Node.getValue().getZExtValue() > N; +} + +AST_MATCHER_P2(Expr, hasSizeOfDescendant, int, Depth, + ast_matchers::internal::Matcher, InnerMatcher) { + if (Depth < 0) + return false; + + const Expr *E = Node.IgnoreParenImpCasts(); + if (InnerMatcher.matches(*E, Finder, Builder)) + return true; + + if (const auto *CE = dyn_cast(E)) { + const auto M = hasSizeOfDescendant(Depth - 1, InnerMatcher); + return M.matches(*CE->getSubExpr(), Finder, Builder); + } else if (const auto *UE = dyn_cast(E)) { + const auto M = hasSizeOfDescendant(Depth - 1, InnerMatcher); + return M.matches(*UE->getSubExpr(), Finder, Builder); + } else if (const auto *BE = dyn_cast(E)) { + const auto LHS = hasSizeOfDescendant(Depth - 1, InnerMatcher); + const auto RHS = hasSizeOfDescendant(Depth - 1, InnerMatcher); + return LHS.matches(*BE->getLHS(), Finder, Builder) || + RHS.matches(*BE->getRHS(), Finder, Builder); + } + + return false; +} + +CharUnits getSizeOfType(const ASTContext &Ctx, const Type *Ty) { + if (!Ty || Ty->isIncompleteType() || Ty->isDependentType() || + isa(Ty) || !Ty->isConstantSizeType()) + return CharUnits::Zero(); + return Ctx.getTypeSizeInChars(Ty); +} + +} // namespace + +SizeofExpressionCheck::SizeofExpressionCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnSizeOfConstant(Options.get("WarnOnSizeOfConstant", 1) != 0), + WarnOnSizeOfThis(Options.get("WarnOnSizeOfThis", 1) != 0), + WarnOnSizeOfCompareToConstant( + Options.get("WarnOnSizeOfCompareToConstant", 1) != 0) {} + +void SizeofExpressionCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnSizeOfConstant", WarnOnSizeOfConstant); + Options.store(Opts, "WarnOnSizeOfThis", WarnOnSizeOfThis); + Options.store(Opts, "WarnOnSizeOfCompareToConstant", + WarnOnSizeOfCompareToConstant); +} + +void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) { + const auto IntegerExpr = ignoringParenImpCasts(integerLiteral()); + const auto ConstantExpr = expr(ignoringParenImpCasts( + anyOf(integerLiteral(), unaryOperator(hasUnaryOperand(IntegerExpr)), + binaryOperator(hasLHS(IntegerExpr), hasRHS(IntegerExpr))))); + const auto SizeOfExpr = + expr(anyOf(sizeOfExpr(has(type())), sizeOfExpr(has(expr())))); + const auto SizeOfZero = expr( + sizeOfExpr(has(ignoringParenImpCasts(expr(integerLiteral(equals(0))))))); + + // Detect expression like: sizeof(ARRAYLEN); + // Note: The expression 'sizeof(sizeof(0))' is a portable trick used to know + // the sizeof size_t. + if (WarnOnSizeOfConstant) { + Finder->addMatcher( + expr(sizeOfExpr(has(ignoringParenImpCasts(ConstantExpr))), + unless(SizeOfZero)) + .bind("sizeof-constant"), + this); + } + + // Detect expression like: sizeof(this); + if (WarnOnSizeOfThis) { + Finder->addMatcher( + expr(sizeOfExpr(has(ignoringParenImpCasts(expr(cxxThisExpr()))))) + .bind("sizeof-this"), + this); + } + + // Detect sizeof(kPtr) where kPtr is 'const char* kPtr = "abc"'; + const auto CharPtrType = pointerType(pointee(isAnyCharacter())); + const auto ConstStrLiteralDecl = + varDecl(isDefinition(), hasType(qualType(hasCanonicalType(CharPtrType))), + hasInitializer(ignoringParenImpCasts(stringLiteral()))); + Finder->addMatcher(expr(sizeOfExpr(has(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(CharPtrType))), + ignoringParenImpCasts(declRefExpr( + hasDeclaration(ConstStrLiteralDecl)))))))) + .bind("sizeof-charp"), + this); + + // Detect sizeof(ptr) where ptr points to an aggregate (i.e. sizeof(&S)). + const auto ArrayExpr = expr(ignoringParenImpCasts( + expr(hasType(qualType(hasCanonicalType(arrayType())))))); + const auto ArrayCastExpr = expr(anyOf( + unaryOperator(hasUnaryOperand(ArrayExpr), unless(hasOperatorName("*"))), + binaryOperator(hasEitherOperand(ArrayExpr)), + castExpr(hasSourceExpression(ArrayExpr)))); + const auto PointerToArrayExpr = expr(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(pointerType(pointee(arrayType())))))))); + + const auto StructAddrOfExpr = + unaryOperator(hasOperatorName("&"), + hasUnaryOperand(ignoringParenImpCasts(expr( + hasType(qualType(hasCanonicalType(recordType()))))))); + + Finder->addMatcher( + expr(sizeOfExpr(has(expr(ignoringParenImpCasts( + anyOf(ArrayCastExpr, PointerToArrayExpr, StructAddrOfExpr)))))) + .bind("sizeof-pointer-to-aggregate"), + this); + + // Detect expression like: sizeof(epxr) <= k for a suspicious constant 'k'. + if (WarnOnSizeOfCompareToConstant) { + Finder->addMatcher( + binaryOperator(matchers::isRelationalOperator(), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr)), + hasEitherOperand(ignoringParenImpCasts( + anyOf(integerLiteral(equals(0)), + integerLiteral(isBiggerThan(0x80000)))))) + .bind("sizeof-compare-constant"), + this); + } + + // Detect expression like: sizeof(expr, expr); most likely an error. + Finder->addMatcher(expr(sizeOfExpr(has(expr(ignoringParenImpCasts( + binaryOperator(hasOperatorName(","))))))) + .bind("sizeof-comma-expr"), + this); + + // Detect sizeof(...) /sizeof(...)); + const auto ElemType = + arrayType(hasElementType(recordType().bind("elem-type"))); + const auto ElemPtrType = pointerType(pointee(type().bind("elem-ptr-type"))); + const auto NumType = qualType(hasCanonicalType( + type(anyOf(ElemType, ElemPtrType, type())).bind("num-type"))); + const auto DenomType = qualType(hasCanonicalType(type().bind("denom-type"))); + + Finder->addMatcher( + binaryOperator(hasOperatorName("/"), + hasLHS(expr(ignoringParenImpCasts( + anyOf(sizeOfExpr(has(NumType)), + sizeOfExpr(has(expr(hasType(NumType)))))))), + hasRHS(expr(ignoringParenImpCasts( + anyOf(sizeOfExpr(has(DenomType)), + sizeOfExpr(has(expr(hasType(DenomType))))))))) + .bind("sizeof-divide-expr"), + this); + + // Detect expression like: sizeof(...) * sizeof(...)); most likely an error. + Finder->addMatcher(binaryOperator(hasOperatorName("*"), + hasLHS(ignoringParenImpCasts(SizeOfExpr)), + hasRHS(ignoringParenImpCasts(SizeOfExpr))) + .bind("sizeof-multiply-sizeof"), + this); + + Finder->addMatcher( + binaryOperator(hasOperatorName("*"), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr)), + hasEitherOperand(ignoringParenImpCasts(binaryOperator( + hasOperatorName("*"), + hasEitherOperand(ignoringParenImpCasts(SizeOfExpr)))))) + .bind("sizeof-multiply-sizeof"), + this); + + // Detect strange double-sizeof expression like: sizeof(sizeof(...)); + // Note: The expression 'sizeof(sizeof(0))' is accepted. + Finder->addMatcher( + expr(sizeOfExpr(has(ignoringParenImpCasts(expr( + hasSizeOfDescendant(8, expr(SizeOfExpr, unless(SizeOfZero)))))))) + .bind("sizeof-sizeof-expr"), + this); +} + +void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Ctx = *Result.Context; + + if (const auto *E = Result.Nodes.getNodeAs("sizeof-constant")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(K)'; did you mean 'K'?"); + } else if (const auto *E = Result.Nodes.getNodeAs("sizeof-this")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(this)'; did you mean 'sizeof(*this)'"); + } else if (const auto *E = Result.Nodes.getNodeAs("sizeof-charp")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(char*)'; do you mean 'strlen'?"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-pointer-to-aggregate")) { + diag(E->getLocStart(), + "suspicious usage of 'sizeof(A*)'; pointer to aggregate"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-compare-constant")) { + diag(E->getLocStart(), + "suspicious comparison of 'sizeof(expr)' to a constant"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-comma-expr")) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(..., ...)'"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-divide-expr")) { + const auto *NumTy = Result.Nodes.getNodeAs("num-type"); + const auto *DenomTy = Result.Nodes.getNodeAs("denom-type"); + const auto *ElementTy = Result.Nodes.getNodeAs("elem-type"); + const auto *PointedTy = Result.Nodes.getNodeAs("elem-ptr-type"); + + CharUnits NumeratorSize = getSizeOfType(Ctx, NumTy); + CharUnits DenominatorSize = getSizeOfType(Ctx, DenomTy); + CharUnits ElementSize = getSizeOfType(Ctx, ElementTy); + + if (DenominatorSize > CharUnits::Zero() && + !NumeratorSize.isMultipleOf(DenominatorSize)) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(...)/sizeof(...)';" + " numerator is not a multiple of denominator"); + } else if (ElementSize > CharUnits::Zero() && + DenominatorSize > CharUnits::Zero() && + ElementSize != DenominatorSize) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(...)/sizeof(...)';" + " numerator is not a multiple of denominator"); + } else if (NumTy && DenomTy && NumTy == DenomTy) { + diag(E->getLocStart(), + "suspicious usage of sizeof pointer 'sizeof(T)/sizeof(T)'"); + } else if (PointedTy && DenomTy && PointedTy == DenomTy) { + diag(E->getLocStart(), + "suspicious usage of sizeof pointer 'sizeof(T*)/sizeof(T)'"); + } else if (NumTy && DenomTy && NumTy->isPointerType() && + DenomTy->isPointerType()) { + diag(E->getLocStart(), + "suspicious usage of sizeof pointer 'sizeof(P*)/sizeof(Q*)'"); + } + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-sizeof-expr")) { + diag(E->getLocStart(), "suspicious usage of 'sizeof(sizeof(...))'"); + } else if (const auto *E = + Result.Nodes.getNodeAs("sizeof-multiply-sizeof")) { + diag(E->getLocStart(), "suspicious 'sizeof' by 'sizeof' multiplication"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SizeofExpressionCheck.h b/clang-tidy/misc/SizeofExpressionCheck.h new file mode 100644 index 000000000..d2ba9daf3 --- /dev/null +++ b/clang-tidy/misc/SizeofExpressionCheck.h @@ -0,0 +1,40 @@ +//===--- SizeofExpressionCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find suspicious usages of sizeof expression. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-sizeof-expression.html +class SizeofExpressionCheck : public ClangTidyCheck { +public: + SizeofExpressionCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool WarnOnSizeOfConstant; + const bool WarnOnSizeOfThis; + const bool WarnOnSizeOfCompareToConstant; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SIZEOF_EXPRESSION_H diff --git a/clang-tidy/misc/StaticAssertCheck.cpp b/clang-tidy/misc/StaticAssertCheck.cpp new file mode 100644 index 000000000..a0e322227 --- /dev/null +++ b/clang-tidy/misc/StaticAssertCheck.cpp @@ -0,0 +1,175 @@ +//===--- 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 IsAlwaysFalse = + expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)), + cxxNullPtrLiteralExpr(), gnuNullExpr())) + .bind("isAlwaysFalse"); + auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf( + IsAlwaysFalse, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse))) + .bind("castExpr"))); + auto AssertExprRoot = anyOf( + binaryOperator( + anyOf(hasOperatorName("&&"), hasOperatorName("==")), + hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))), + anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)), + anything())) + .bind("assertExprRoot"), + IsAlwaysFalse); + auto NonConstexprFunctionCall = + callExpr(hasDeclaration(functionDecl(unless(isConstexpr())))); + auto AssertCondition = + expr( + anyOf(expr(ignoringParenCasts(anyOf( + AssertExprRoot, unaryOperator(hasUnaryOperand( + ignoringParenCasts(AssertExprRoot)))))), + anything()), + unless(findAll(NonConstexprFunctionCall))) + .bind("condition"); + auto Condition = + anyOf(ignoringParenImpCasts(callExpr( + hasDeclaration(functionDecl(hasName("__builtin_expect"))), + hasArgument(0, AssertCondition))), + AssertCondition); + + Finder->addMatcher(conditionalOperator(hasCondition(Condition), + unless(isInTemplateInstantiation())) + .bind("condStmt"), + this); + + Finder->addMatcher( + ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation())) + .bind("condStmt"), + this); +} + +void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext *ASTCtx = Result.Context; + const LangOptions &Opts = ASTCtx->getLangOpts(); + const SourceManager &SM = ASTCtx->getSourceManager(); + const auto *CondStmt = Result.Nodes.getNodeAs("condStmt"); + const auto *Condition = Result.Nodes.getNodeAs("condition"); + const auto *IsAlwaysFalse = Result.Nodes.getNodeAs("isAlwaysFalse"); + const auto *AssertMSG = Result.Nodes.getNodeAs("assertMSG"); + const auto *AssertExprRoot = + Result.Nodes.getNodeAs("assertExprRoot"); + const auto *CastExpr = Result.Nodes.getNodeAs("castExpr"); + SourceLocation AssertExpansionLoc = CondStmt->getLocStart(); + + if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID()) + return; + + StringRef MacroName = + Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts); + + if (MacroName != "assert" || Condition->isValueDependent() || + Condition->isTypeDependent() || Condition->isInstantiationDependent() || + !Condition->isEvaluatable(*ASTCtx)) + return; + + // False literal is not the result of macro expansion. + if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) { + SourceLocation FalseLiteralLoc = + SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc()); + if (!FalseLiteralLoc.isMacroID()) + return; + + StringRef FalseMacroName = + Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts); + if (FalseMacroName.compare_lower("false") == 0 || + FalseMacroName.compare_lower("null") == 0) + return; + } + + SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc); + + SmallVector FixItHints; + SourceLocation LastParenLoc; + if (AssertLoc.isValid() && !AssertLoc.isMacroID() && + (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) { + FixItHints.push_back( + FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert")); + + std::string StaticAssertMSG = ", \"\""; + if (AssertExprRoot) { + FixItHints.push_back(FixItHint::CreateRemoval( + SourceRange(AssertExprRoot->getOperatorLoc()))); + FixItHints.push_back(FixItHint::CreateRemoval( + SourceRange(AssertMSG->getLocStart(), AssertMSG->getLocEnd()))); + StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str(); + } + + FixItHints.push_back( + FixItHint::CreateInsertion(LastParenLoc, StaticAssertMSG)); + } + + diag(AssertLoc, "found assert() that could be replaced by static_assert()") + << FixItHints; +} + +SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx, + SourceLocation AssertLoc) { + const LangOptions &Opts = ASTCtx->getLangOpts(); + const SourceManager &SM = ASTCtx->getSourceManager(); + + llvm::MemoryBuffer *Buffer = SM.getBuffer(SM.getFileID(AssertLoc)); + if (!Buffer) + return SourceLocation(); + + const char *BufferPos = SM.getCharacterData(AssertLoc); + + Token Token; + Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts, + Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd()); + + // assert first left parenthesis + if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) || + !Token.is(tok::l_paren)) + return SourceLocation(); + + unsigned int ParenCount = 1; + while (ParenCount && !Lexer.LexFromRawLexer(Token)) { + if (Token.is(tok::l_paren)) + ++ParenCount; + else if (Token.is(tok::r_paren)) + --ParenCount; + } + + return Token.getLocation(); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StaticAssertCheck.h b/clang-tidy/misc/StaticAssertCheck.h new file mode 100644 index 000000000..faefce172 --- /dev/null +++ b/clang-tidy/misc/StaticAssertCheck.h @@ -0,0 +1,41 @@ +//===--- StaticAssertCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Replaces `assert()` with `static_assert()` if the condition is evaluatable +/// at compile time. +/// +/// The condition of `static_assert()` is evaluated at compile time which is +/// safer and more efficient. +class StaticAssertCheck : public ClangTidyCheck { +public: + StaticAssertCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + SourceLocation getLastParenLoc(const ASTContext *ASTCtx, + SourceLocation AssertLoc); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STATICASSERTCHECK_H diff --git a/clang-tidy/misc/StringCompareCheck.cpp b/clang-tidy/misc/StringCompareCheck.cpp new file mode 100644 index 000000000..b6a429533 --- /dev/null +++ b/clang-tidy/misc/StringCompareCheck.cpp @@ -0,0 +1,82 @@ +//===--- MiscStringCompare.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringCompareCheck.h" +#include "../utils/FixItHintUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static const StringRef CompareMessage = "do not use 'compare' to test equality " + "of strings; use the string equality " + "operator instead"; + +void StringCompareCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto StrCompare = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("compare"), + ofClass(classTemplateSpecializationDecl( + hasName("::std::basic_string"))))), + hasArgument(0, expr().bind("str2")), argumentCountIs(1), + callee(memberExpr().bind("str1"))); + + // First and second case: cast str.compare(str) to boolean. + Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()), + has(StrCompare)) + .bind("match1"), + this); + + // Third and fourth case: str.compare(str) == 0 and str.compare(str) != 0. + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(StrCompare.bind("compare")), + hasEitherOperand(integerLiteral(equals(0)).bind("zero"))) + .bind("match2"), + this); +} + +void StringCompareCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Matched = Result.Nodes.getNodeAs("match1")) { + diag(Matched->getLocStart(), CompareMessage); + return; + } + + if (const auto *Matched = Result.Nodes.getNodeAs("match2")) { + const ASTContext &Ctx = *Result.Context; + + if (const auto *Zero = Result.Nodes.getNodeAs("zero")) { + const auto *Str1 = Result.Nodes.getNodeAs("str1"); + const auto *Str2 = Result.Nodes.getNodeAs("str2"); + const auto *Compare = Result.Nodes.getNodeAs("compare"); + + auto Diag = diag(Matched->getLocStart(), CompareMessage); + + if (Str1->isArrow()) + Diag << FixItHint::CreateInsertion(Str1->getLocStart(), "*"); + + Diag << tooling::fixit::createReplacement(*Zero, *Str2, Ctx) + << tooling::fixit::createReplacement(*Compare, *Str1->getBase(), + Ctx); + } + } + + // FIXME: Add fixit to fix the code for case one and two (match1). +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringCompareCheck.h b/clang-tidy/misc/StringCompareCheck.h new file mode 100644 index 000000000..2f3a7d65e --- /dev/null +++ b/clang-tidy/misc/StringCompareCheck.h @@ -0,0 +1,36 @@ +//===--- StringCompareCheck.h - clang-tidy-----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_COMPARE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_COMPARE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check flags all calls compare when used to check for string +/// equality or inequality. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-compare.html +class StringCompareCheck : public ClangTidyCheck { +public: + StringCompareCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_COMPARE_H diff --git a/clang-tidy/misc/StringConstructorCheck.cpp b/clang-tidy/misc/StringConstructorCheck.cpp new file mode 100644 index 000000000..7ff488133 --- /dev/null +++ b/clang-tidy/misc/StringConstructorCheck.cpp @@ -0,0 +1,134 @@ +//===--- StringConstructorCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringConstructorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +AST_MATCHER_P(IntegerLiteral, isBiggerThan, unsigned, N) { + return Node.getValue().getZExtValue() > N; +} + +StringConstructorCheck::StringConstructorCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnLargeLength(Options.get("WarnOnLargeLength", 1) != 0), + LargeLengthThreshold(Options.get("LargeLengthThreshold", 0x800000)) {} + +void StringConstructorCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnLargeLength", WarnOnLargeLength); + Options.store(Opts, "LargeLengthThreshold", LargeLengthThreshold); +} + +void StringConstructorCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto ZeroExpr = expr(ignoringParenImpCasts(integerLiteral(equals(0)))); + const auto CharExpr = expr(ignoringParenImpCasts(characterLiteral())); + const auto NegativeExpr = expr(ignoringParenImpCasts( + unaryOperator(hasOperatorName("-"), + hasUnaryOperand(integerLiteral(unless(equals(0))))))); + const auto LargeLengthExpr = expr(ignoringParenImpCasts( + integerLiteral(isBiggerThan(LargeLengthThreshold)))); + const auto CharPtrType = type(anyOf(pointerType(), arrayType())); + + // Match a string-literal; even through a declaration with initializer. + const auto BoundStringLiteral = stringLiteral().bind("str"); + const auto ConstStrLiteralDecl = varDecl( + isDefinition(), hasType(constantArrayType()), hasType(isConstQualified()), + hasInitializer(ignoringParenImpCasts(BoundStringLiteral))); + const auto ConstPtrStrLiteralDecl = varDecl( + isDefinition(), + hasType(pointerType(pointee(isAnyCharacter(), isConstQualified()))), + hasInitializer(ignoringParenImpCasts(BoundStringLiteral))); + const auto ConstStrLiteral = expr(ignoringParenImpCasts(anyOf( + BoundStringLiteral, declRefExpr(hasDeclaration(anyOf( + ConstPtrStrLiteralDecl, ConstStrLiteralDecl)))))); + + // Check the fill constructor. Fills the string with n consecutive copies of + // character c. [i.e string(size_t n, char c);]. + Finder->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(0, hasType(qualType(isInteger()))), + hasArgument(1, hasType(qualType(isInteger()))), + anyOf( + // Detect the expression: string('x', 40); + hasArgument(0, CharExpr.bind("swapped-parameter")), + // Detect the expression: string(0, ...); + hasArgument(0, ZeroExpr.bind("empty-string")), + // Detect the expression: string(-4, ...); + hasArgument(0, NegativeExpr.bind("negative-length")), + // Detect the expression: string(0x1234567, ...); + hasArgument(0, LargeLengthExpr.bind("large-length")))) + .bind("constructor"), + this); + + // Check the literal string constructor with char pointer and length + // parameters. [i.e. string (const char* s, size_t n);] + Finder->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(0, hasType(CharPtrType)), + hasArgument(1, hasType(isInteger())), + anyOf( + // Detect the expression: string("...", 0); + hasArgument(1, ZeroExpr.bind("empty-string")), + // Detect the expression: string("...", -4); + hasArgument(1, NegativeExpr.bind("negative-length")), + // Detect the expression: string("lit", 0x1234567); + hasArgument(1, LargeLengthExpr.bind("large-length")), + // Detect the expression: string("lit", 5) + allOf(hasArgument(0, ConstStrLiteral.bind("literal-with-length")), + hasArgument(1, ignoringParenImpCasts( + integerLiteral().bind("int")))))) + .bind("constructor"), + this); +} + +void StringConstructorCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Ctx = *Result.Context; + const auto *E = Result.Nodes.getNodeAs("constructor"); + assert(E && "missing constructor expression"); + SourceLocation Loc = E->getLocStart(); + + if (Result.Nodes.getNodeAs("swapped-parameter")) { + const Expr *P0 = E->getArg(0); + const Expr *P1 = E->getArg(1); + diag(Loc, "string constructor parameters are probably swapped;" + " expecting string(count, character)") + << tooling::fixit::createReplacement(*P0, *P1, Ctx) + << tooling::fixit::createReplacement(*P1, *P0, Ctx); + } else if (Result.Nodes.getNodeAs("empty-string")) { + diag(Loc, "constructor creating an empty string"); + } else if (Result.Nodes.getNodeAs("negative-length")) { + diag(Loc, "negative value used as length parameter"); + } else if (Result.Nodes.getNodeAs("large-length")) { + if (WarnOnLargeLength) + diag(Loc, "suspicious large length parameter"); + } else if (Result.Nodes.getNodeAs("literal-with-length")) { + const auto *Str = Result.Nodes.getNodeAs("str"); + const auto *Lit = Result.Nodes.getNodeAs("int"); + if (Lit->getValue().ugt(Str->getLength())) { + diag(Loc, "length is bigger then string literal size"); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringConstructorCheck.h b/clang-tidy/misc/StringConstructorCheck.h new file mode 100644 index 000000000..ca32fb6f9 --- /dev/null +++ b/clang-tidy/misc/StringConstructorCheck.h @@ -0,0 +1,39 @@ +//===--- StringConstructorCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_CONSTRUCTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_CONSTRUCTOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds suspicious string constructor and check their parameters. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-constructor.html +class StringConstructorCheck : public ClangTidyCheck { +public: + StringConstructorCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool WarnOnLargeLength; + const unsigned int LargeLengthThreshold; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_CONSTRUCTOR_H diff --git a/clang-tidy/misc/StringIntegerAssignmentCheck.cpp b/clang-tidy/misc/StringIntegerAssignmentCheck.cpp new file mode 100644 index 000000000..4e8c488d0 --- /dev/null +++ b/clang-tidy/misc/StringIntegerAssignmentCheck.cpp @@ -0,0 +1,86 @@ +//===--- StringIntegerAssignmentCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringIntegerAssignmentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf(hasOverloadedOperatorName("="), + hasOverloadedOperatorName("+=")), + callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl( + hasName("::std::basic_string"), + hasTemplateArgument(0, refersToType(qualType().bind("type"))))))), + hasArgument(1, + ignoringImpCasts(expr(hasType(isInteger()), + unless(hasType(isAnyCharacter()))) + .bind("expr"))), + unless(isInTemplateInstantiation())), + this); +} + +void StringIntegerAssignmentCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Argument = Result.Nodes.getNodeAs("expr"); + SourceLocation Loc = Argument->getLocStart(); + + auto Diag = + diag(Loc, "an integer is interpreted as a character code when assigning " + "it to a string; if this is intended, cast the integer to the " + "appropriate character type; if you want a string " + "representation, use the appropriate conversion facility"); + + if (Loc.isMacroID()) + return; + + auto CharType = *Result.Nodes.getNodeAs("type"); + bool IsWideCharType = CharType->isWideCharType(); + if (!CharType->isCharType() && !IsWideCharType) + return; + bool IsOneDigit = false; + bool IsLiteral = false; + if (const auto *Literal = dyn_cast(Argument)) { + IsOneDigit = Literal->getValue().getLimitedValue() < 10; + IsLiteral = true; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Argument->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + if (IsOneDigit) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'") + << FixItHint::CreateInsertion(EndLoc, "'"); + return; + } + if (IsLiteral) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"") + << FixItHint::CreateInsertion(EndLoc, "\""); + return; + } + + if (getLangOpts().CPlusPlus11) { + Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring(" + : "std::to_string(") + << FixItHint::CreateInsertion(EndLoc, ")"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringIntegerAssignmentCheck.h b/clang-tidy/misc/StringIntegerAssignmentCheck.h new file mode 100644 index 000000000..7963362ac --- /dev/null +++ b/clang-tidy/misc/StringIntegerAssignmentCheck.h @@ -0,0 +1,35 @@ +//===--- StringIntegerAssignmentCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds instances where an integer is assigned to a string. +/// +/// For more details see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-assignment.html +class StringIntegerAssignmentCheck : public ClangTidyCheck { +public: + StringIntegerAssignmentCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_INTEGER_ASSIGNMENT_H diff --git a/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp new file mode 100644 index 000000000..335927b2e --- /dev/null +++ b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.cpp @@ -0,0 +1,83 @@ +//===--- StringLiteralWithEmbeddedNulCheck.cpp - clang-tidy----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StringLiteralWithEmbeddedNulCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +AST_MATCHER(StringLiteral, containsNul) { + for (size_t i = 0; i < Node.getLength(); ++i) + if (Node.getCodeUnit(i) == '\0') + return true; + return false; +} + +void StringLiteralWithEmbeddedNulCheck::registerMatchers(MatchFinder *Finder) { + // Match a string that contains embedded NUL character. Extra-checks are + // applied in |check| to find incorectly escaped characters. + Finder->addMatcher(stringLiteral(containsNul()).bind("strlit"), this); + + // The remaining checks only apply to C++. + if (!getLangOpts().CPlusPlus) + return; + + const auto StrLitWithNul = + ignoringParenImpCasts(stringLiteral(containsNul()).bind("truncated")); + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + cxxConstructExpr(argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(1, cxxDefaultArgExpr())))); + + // Detect passing a suspicious string literal to a string constructor. + // example: std::string str = "abc\0def"; + Finder->addMatcher( + cxxConstructExpr(StringConstructorExpr, hasArgument(0, StrLitWithNul)), + this); + + // Detect passing a suspicious string literal through an overloaded operator. + Finder->addMatcher(cxxOperatorCallExpr(hasAnyArgument(StrLitWithNul)), this); +} + +void StringLiteralWithEmbeddedNulCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *SL = Result.Nodes.getNodeAs("strlit")) { + for (size_t Offset = 0, Length = SL->getLength(); Offset < Length; + ++Offset) { + // Find a sequence of character like "\0x12". + if (Offset + 3 < Length && SL->getCodeUnit(Offset) == '\0' && + SL->getCodeUnit(Offset + 1) == 'x' && + isDigit(SL->getCodeUnit(Offset + 2)) && + isDigit(SL->getCodeUnit(Offset + 3))) { + diag(SL->getLocStart(), "suspicious embedded NUL character"); + return; + } + } + } + + if (const auto *SL = Result.Nodes.getNodeAs("truncated")) { + diag(SL->getLocStart(), + "truncated string literal with embedded NUL character"); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h new file mode 100644 index 000000000..e4a87fc28 --- /dev/null +++ b/clang-tidy/misc/StringLiteralWithEmbeddedNulCheck.h @@ -0,0 +1,35 @@ +//===--- StringLiteralWithEmbeddedNulCheck.h - clang-tidy--------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_LITERAL_WITH_EMBEDDED_NUL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_LITERAL_WITH_EMBEDDED_NUL_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find suspicious string literals with embedded NUL characters. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-string-literal-with-embedded-nul.html +class StringLiteralWithEmbeddedNulCheck : public ClangTidyCheck { +public: + StringLiteralWithEmbeddedNulCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_STRING_LITERAL_WITH_EMBEDDED_NUL_H diff --git a/clang-tidy/misc/SuspiciousEnumUsageCheck.cpp b/clang-tidy/misc/SuspiciousEnumUsageCheck.cpp new file mode 100644 index 000000000..059c0d2db --- /dev/null +++ b/clang-tidy/misc/SuspiciousEnumUsageCheck.cpp @@ -0,0 +1,216 @@ +//===--- SuspiciousEnumUsageCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousEnumUsageCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +static const char DifferentEnumErrorMessage[] = + "enum values are from different enum types"; + +static const char BitmaskErrorMessage[] = + "enum type seems like a bitmask (contains mostly " + "power-of-2 literals), but this literal is not a " + "power-of-2"; + +static const char BitmaskVarErrorMessage[] = + "enum type seems like a bitmask (contains mostly " + "power-of-2 literals) but %plural{1:a literal is|:some literals are}0 not " + "power-of-2"; + +static const char BitmaskNoteMessage[] = "used here as a bitmask"; + +/// Stores a min and a max value which describe an interval. +struct ValueRange { + llvm::APSInt MinVal; + llvm::APSInt MaxVal; + + ValueRange(const EnumDecl *EnumDec) { + const auto MinMaxVal = std::minmax_element( + EnumDec->enumerator_begin(), EnumDec->enumerator_end(), + [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) { + return E1->getInitVal() < E2->getInitVal(); + }); + MinVal = MinMaxVal.first->getInitVal(); + MaxVal = MinMaxVal.second->getInitVal(); + } +}; + +/// Return the number of EnumConstantDecls in an EnumDecl. +static int enumLength(const EnumDecl *EnumDec) { + return std::distance(EnumDec->enumerator_begin(), EnumDec->enumerator_end()); +} + +static bool hasDisjointValueRange(const EnumDecl *Enum1, + const EnumDecl *Enum2) { + ValueRange Range1(Enum1), Range2(Enum2); + return (Range1.MaxVal < Range2.MinVal) || (Range2.MaxVal < Range1.MinVal); +} + +static bool isNonPowerOf2NorNullLiteral(const EnumConstantDecl *EnumConst) { + llvm::APSInt Val = EnumConst->getInitVal(); + if (Val.isPowerOf2() || !Val.getBoolValue()) + return false; + const Expr *InitExpr = EnumConst->getInitExpr(); + if (!InitExpr) + return true; + return isa(InitExpr->IgnoreImpCasts()); +} + +static bool isMaxValAllBitSetLiteral(const EnumDecl *EnumDec) { + auto EnumConst = std::max_element( + EnumDec->enumerator_begin(), EnumDec->enumerator_end(), + [](const EnumConstantDecl *E1, const EnumConstantDecl *E2) { + return E1->getInitVal() < E2->getInitVal(); + }); + + if (const Expr *InitExpr = EnumConst->getInitExpr()) { + return EnumConst->getInitVal().countTrailingOnes() == + EnumConst->getInitVal().getActiveBits() && + isa(InitExpr->IgnoreImpCasts()); + } + return false; +} + +static int countNonPowOfTwoLiteralNum(const EnumDecl *EnumDec) { + return std::count_if( + EnumDec->enumerator_begin(), EnumDec->enumerator_end(), + [](const EnumConstantDecl *E) { return isNonPowerOf2NorNullLiteral(E); }); +} + +/// Check if there is one or two enumerators that are not a power of 2 and are +/// initialized by a literal in the enum type, and that the enumeration contains +/// enough elements to reasonably act as a bitmask. Exclude the case where the +/// last enumerator is the sum of the lesser values (and initialized by a +/// literal) or when it could contain consecutive values. +static bool isPossiblyBitMask(const EnumDecl *EnumDec) { + ValueRange VR(EnumDec); + int EnumLen = enumLength(EnumDec); + int NonPowOfTwoCounter = countNonPowOfTwoLiteralNum(EnumDec); + return NonPowOfTwoCounter >= 1 && NonPowOfTwoCounter <= 2 && + NonPowOfTwoCounter < EnumLen / 2 && + (VR.MaxVal - VR.MinVal != EnumLen - 1) && + !(NonPowOfTwoCounter == 1 && isMaxValAllBitSetLiteral(EnumDec)); +} + +SuspiciousEnumUsageCheck::SuspiciousEnumUsageCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), StrictMode(Options.get("StrictMode", 0)) {} + +void SuspiciousEnumUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StrictMode", StrictMode); +} + +void SuspiciousEnumUsageCheck::registerMatchers(MatchFinder *Finder) { + const auto enumExpr = [](StringRef RefName, StringRef DeclName) { + return allOf(ignoringImpCasts(expr().bind(RefName)), + ignoringImpCasts(hasType(enumDecl().bind(DeclName)))); + }; + + Finder->addMatcher( + binaryOperator(hasOperatorName("|"), hasLHS(enumExpr("", "enumDecl")), + hasRHS(allOf(enumExpr("", "otherEnumDecl"), + ignoringImpCasts(hasType(enumDecl( + unless(equalsBoundNode("enumDecl")))))))) + .bind("diffEnumOp"), + this); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")), + hasLHS(enumExpr("lhsExpr", "enumDecl")), + hasRHS(allOf(enumExpr("rhsExpr", ""), + ignoringImpCasts(hasType(enumDecl( + equalsBoundNode("enumDecl"))))))), + this); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("|")), + hasEitherOperand( + allOf(hasType(isInteger()), unless(enumExpr("", "")))), + hasEitherOperand(enumExpr("enumExpr", "enumDecl"))), + this); + + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("|="), hasOperatorName("+=")), + hasRHS(enumExpr("enumExpr", "enumDecl"))), + this); +} + +void SuspiciousEnumUsageCheck::checkSuspiciousBitmaskUsage( + const Expr *NodeExpr, const EnumDecl *EnumDec) { + const auto *EnumExpr = dyn_cast(NodeExpr); + const auto *EnumConst = + EnumExpr ? dyn_cast(EnumExpr->getDecl()) : nullptr; + + // Report the parameter if neccessary. + if (!EnumConst) { + diag(EnumDec->getInnerLocStart(), BitmaskVarErrorMessage) + << countNonPowOfTwoLiteralNum(EnumDec); + diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note); + } else if (isNonPowerOf2NorNullLiteral(EnumConst)) { + diag(EnumConst->getSourceRange().getBegin(), BitmaskErrorMessage); + diag(EnumExpr->getExprLoc(), BitmaskNoteMessage, DiagnosticIDs::Note); + } +} + +void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) { + // Case 1: The two enum values come from different types. + if (const auto *DiffEnumOp = + Result.Nodes.getNodeAs("diffEnumOp")) { + const auto *EnumDec = Result.Nodes.getNodeAs("enumDecl"); + const auto *OtherEnumDec = + Result.Nodes.getNodeAs("otherEnumDecl"); + // Skip when one of the parameters is an empty enum. The + // hasDisjointValueRange function could not decide the values properly in + // case of an empty enum. + if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() || + OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end()) + return; + + if (!hasDisjointValueRange(EnumDec, OtherEnumDec)) + diag(DiffEnumOp->getOperatorLoc(), DifferentEnumErrorMessage); + return; + } + + // Case 2 and 3 only checked in strict mode. The checker tries to detect + // suspicious bitmasks which contains values initialized by non power-of-2 + // literals. + if (!StrictMode) + return; + const auto *EnumDec = Result.Nodes.getNodeAs("enumDecl"); + if (!isPossiblyBitMask(EnumDec)) + return; + + // Case 2: + // a. Investigating the right hand side of `+=` or `|=` operator. + // b. When the operator is `|` or `+` but only one of them is an EnumExpr + if (const auto *EnumExpr = Result.Nodes.getNodeAs("enumExpr")) { + checkSuspiciousBitmaskUsage(EnumExpr, EnumDec); + return; + } + + // Case 3: + // '|' or '+' operator where both argument comes from the same enum type + const auto *LhsExpr = Result.Nodes.getNodeAs("lhsExpr"); + checkSuspiciousBitmaskUsage(LhsExpr, EnumDec); + + const auto *RhsExpr = Result.Nodes.getNodeAs("rhsExpr"); + checkSuspiciousBitmaskUsage(RhsExpr, EnumDec); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousEnumUsageCheck.h b/clang-tidy/misc/SuspiciousEnumUsageCheck.h new file mode 100644 index 000000000..d53217927 --- /dev/null +++ b/clang-tidy/misc/SuspiciousEnumUsageCheck.h @@ -0,0 +1,39 @@ +//===--- SuspiciousEnumUsageCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_ENUM_USAGE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_ENUM_USAGE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The checker detects various cases when an enum is probably misused (as a +/// bitmask). +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-enum-usage.html +class SuspiciousEnumUsageCheck : public ClangTidyCheck { +public: + SuspiciousEnumUsageCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void checkSuspiciousBitmaskUsage(const Expr*, const EnumDecl*); + const bool StrictMode; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_ENUM_USAGE_H diff --git a/clang-tidy/misc/SuspiciousMissingCommaCheck.cpp b/clang-tidy/misc/SuspiciousMissingCommaCheck.cpp new file mode 100644 index 000000000..a3ed38fb3 --- /dev/null +++ b/clang-tidy/misc/SuspiciousMissingCommaCheck.cpp @@ -0,0 +1,129 @@ +//===--- SuspiciousMissingCommaCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousMissingCommaCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +bool isConcatenatedLiteralsOnPurpose(ASTContext *Ctx, + const StringLiteral *Lit) { + // String literals surrounded by parentheses are assumed to be on purpose. + // i.e.: const char* Array[] = { ("a" "b" "c"), "d", [...] }; + auto Parents = Ctx->getParents(*Lit); + if (Parents.size() == 1 && Parents[0].get() != nullptr) + return true; + + // Appropriately indented string literals are assumed to be on purpose. + // The following frequent indentation is accepted: + // const char* Array[] = { + // "first literal" + // "indented literal" + // "indented literal", + // "second literal", + // [...] + // }; + const SourceManager &SM = Ctx->getSourceManager(); + bool IndentedCorrectly = true; + SourceLocation FirstToken = Lit->getStrTokenLoc(0); + FileID BaseFID = SM.getFileID(FirstToken); + unsigned int BaseIndent = SM.getSpellingColumnNumber(FirstToken); + unsigned int BaseLine = SM.getSpellingLineNumber(FirstToken); + for (unsigned int TokNum = 1; TokNum < Lit->getNumConcatenated(); ++TokNum) { + SourceLocation Token = Lit->getStrTokenLoc(TokNum); + FileID FID = SM.getFileID(Token); + unsigned int Indent = SM.getSpellingColumnNumber(Token); + unsigned int Line = SM.getSpellingLineNumber(Token); + if (FID != BaseFID || Line != BaseLine + TokNum || Indent <= BaseIndent) { + IndentedCorrectly = false; + break; + } + } + if (IndentedCorrectly) + return true; + + // There is no pattern recognized by the checker, assume it's not on purpose. + return false; +} + +AST_MATCHER_P(StringLiteral, isConcatenatedLiteral, unsigned, + MaxConcatenatedTokens) { + return Node.getNumConcatenated() > 1 && + Node.getNumConcatenated() < MaxConcatenatedTokens && + !isConcatenatedLiteralsOnPurpose(&Finder->getASTContext(), &Node); +} + +} // namespace + +SuspiciousMissingCommaCheck::SuspiciousMissingCommaCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SizeThreshold(Options.get("SizeThreshold", 5U)), + RatioThreshold(std::stod(Options.get("RatioThreshold", ".2"))), + MaxConcatenatedTokens(Options.get("MaxConcatenatedTokens", 5U)) {} + +void SuspiciousMissingCommaCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "SizeThreshold", SizeThreshold); + Options.store(Opts, "RatioThreshold", std::to_string(RatioThreshold)); + Options.store(Opts, "MaxConcatenatedTokens", MaxConcatenatedTokens); +} + +void SuspiciousMissingCommaCheck::registerMatchers(MatchFinder *Finder) { + const auto ConcatenatedStringLiteral = + stringLiteral(isConcatenatedLiteral(MaxConcatenatedTokens)).bind("str"); + + const auto StringsInitializerList = + initListExpr(hasType(constantArrayType()), + has(ignoringParenImpCasts(expr(ConcatenatedStringLiteral)))); + + Finder->addMatcher(StringsInitializerList.bind("list"), this); +} + +void SuspiciousMissingCommaCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *InitializerList = Result.Nodes.getNodeAs("list"); + const auto *ConcatenatedLiteral = + Result.Nodes.getNodeAs("str"); + assert(InitializerList && ConcatenatedLiteral); + + // Skip small arrays as they often generate false-positive. + unsigned int Size = InitializerList->getNumInits(); + if (Size < SizeThreshold) + return; + + // Count the number of occurence of concatenated string literal. + unsigned int Count = 0; + for (unsigned int i = 0; i < Size; ++i) { + const Expr *Child = InitializerList->getInit(i)->IgnoreImpCasts(); + if (const auto *Literal = dyn_cast(Child)) { + if (Literal->getNumConcatenated() > 1) + ++Count; + } + } + + // Warn only when concatenation is not common in this initializer list. + // The current threshold is set to less than 1/5 of the string literals. + if (double(Count) / Size > RatioThreshold) + return; + + diag(ConcatenatedLiteral->getLocStart(), + "suspicious string literal, probably missing a comma"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousMissingCommaCheck.h b/clang-tidy/misc/SuspiciousMissingCommaCheck.h new file mode 100644 index 000000000..bf74ad11c --- /dev/null +++ b/clang-tidy/misc/SuspiciousMissingCommaCheck.h @@ -0,0 +1,44 @@ +//===--- SuspiciousMissingCommaCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_MISSING_COMMA_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_MISSING_COMMA_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check finds string literals which are probably concatenated +/// accidentally. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-missing-comma.html +class SuspiciousMissingCommaCheck : public ClangTidyCheck { +public: + SuspiciousMissingCommaCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + // Minimal size of a string literals array to be considered by the checker. + const unsigned SizeThreshold; + // Maximal threshold ratio of suspicious string literals to be considered. + const double RatioThreshold; + // Maximal number of concatenated tokens. + const unsigned MaxConcatenatedTokens; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_MISSING_COMMA_H diff --git a/clang-tidy/misc/SuspiciousSemicolonCheck.cpp b/clang-tidy/misc/SuspiciousSemicolonCheck.cpp new file mode 100644 index 000000000..1d26de9eb --- /dev/null +++ b/clang-tidy/misc/SuspiciousSemicolonCheck.cpp @@ -0,0 +1,77 @@ +//===--- SuspiciousSemicolonCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousSemicolonCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SuspiciousSemicolonCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + stmt(anyOf(ifStmt(hasThen(nullStmt().bind("semi")), + unless(hasElse(stmt()))), + forStmt(hasBody(nullStmt().bind("semi"))), + cxxForRangeStmt(hasBody(nullStmt().bind("semi"))), + whileStmt(hasBody(nullStmt().bind("semi"))))) + .bind("stmt"), + this); +} + +void SuspiciousSemicolonCheck::check(const MatchFinder::MatchResult &Result) { + if (Result.Context->getDiagnostics().hasErrorOccurred()) + return; + + const auto *Semicolon = Result.Nodes.getNodeAs("semi"); + SourceLocation LocStart = Semicolon->getLocStart(); + + if (LocStart.isMacroID()) + return; + + ASTContext &Ctxt = *Result.Context; + auto Token = utils::lexer::getPreviousNonCommentToken(Ctxt, LocStart); + auto &SM = *Result.SourceManager; + unsigned SemicolonLine = SM.getSpellingLineNumber(LocStart); + + const auto *Statement = Result.Nodes.getNodeAs("stmt"); + const bool IsIfStmt = isa(Statement); + + if (!IsIfStmt && + SM.getSpellingLineNumber(Token.getLocation()) != SemicolonLine) + return; + + SourceLocation LocEnd = Semicolon->getLocEnd(); + FileID FID = SM.getFileID(LocEnd); + llvm::MemoryBuffer *Buffer = SM.getBuffer(FID, LocEnd); + Lexer Lexer(SM.getLocForStartOfFile(FID), Ctxt.getLangOpts(), + Buffer->getBufferStart(), SM.getCharacterData(LocEnd) + 1, + Buffer->getBufferEnd()); + if (Lexer.LexFromRawLexer(Token)) + return; + + unsigned BaseIndent = SM.getSpellingColumnNumber(Statement->getLocStart()); + unsigned NewTokenIndent = SM.getSpellingColumnNumber(Token.getLocation()); + unsigned NewTokenLine = SM.getSpellingLineNumber(Token.getLocation()); + + if (!IsIfStmt && NewTokenIndent <= BaseIndent && + Token.getKind() != tok::l_brace && NewTokenLine != SemicolonLine) + return; + + diag(LocStart, "potentially unintended semicolon") + << FixItHint::CreateRemoval(SourceRange(LocStart, LocEnd)); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousSemicolonCheck.h b/clang-tidy/misc/SuspiciousSemicolonCheck.h new file mode 100644 index 000000000..6791ba6c7 --- /dev/null +++ b/clang-tidy/misc/SuspiciousSemicolonCheck.h @@ -0,0 +1,36 @@ +//===--- SuspiciousSemicolonCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_SEMICOLON_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_SEMICOLON_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// This check finds semicolon that modifies the meaning of the program +/// unintendedly. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-semicolon.html +class SuspiciousSemicolonCheck : public ClangTidyCheck { +public: + SuspiciousSemicolonCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_SEMICOLON_H diff --git a/clang-tidy/misc/SuspiciousStringCompareCheck.cpp b/clang-tidy/misc/SuspiciousStringCompareCheck.cpp new file mode 100644 index 000000000..641200cbb --- /dev/null +++ b/clang-tidy/misc/SuspiciousStringCompareCheck.cpp @@ -0,0 +1,218 @@ +//===--- SuspiciousStringCompareCheck.cpp - clang-tidy---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousStringCompareCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +// Semicolon separated list of known string compare-like functions. The list +// must ends with a semicolon. +static const char KnownStringCompareFunctions[] = "__builtin_memcmp;" + "__builtin_strcasecmp;" + "__builtin_strcmp;" + "__builtin_strncasecmp;" + "__builtin_strncmp;" + "_mbscmp;" + "_mbscmp_l;" + "_mbsicmp;" + "_mbsicmp_l;" + "_mbsnbcmp;" + "_mbsnbcmp_l;" + "_mbsnbicmp;" + "_mbsnbicmp_l;" + "_mbsncmp;" + "_mbsncmp_l;" + "_mbsnicmp;" + "_mbsnicmp_l;" + "_memicmp;" + "_memicmp_l;" + "_stricmp;" + "_stricmp_l;" + "_strnicmp;" + "_strnicmp_l;" + "_wcsicmp;" + "_wcsicmp_l;" + "_wcsnicmp;" + "_wcsnicmp_l;" + "lstrcmp;" + "lstrcmpi;" + "memcmp;" + "memicmp;" + "strcasecmp;" + "strcmp;" + "strcmpi;" + "stricmp;" + "strncasecmp;" + "strncmp;" + "strnicmp;" + "wcscasecmp;" + "wcscmp;" + "wcsicmp;" + "wcsncmp;" + "wcsnicmp;" + "wmemcmp;"; + +SuspiciousStringCompareCheck::SuspiciousStringCompareCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnImplicitComparison(Options.get("WarnOnImplicitComparison", 1)), + WarnOnLogicalNotComparison(Options.get("WarnOnLogicalNotComparison", 0)), + StringCompareLikeFunctions( + Options.get("StringCompareLikeFunctions", "")) {} + +void SuspiciousStringCompareCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnImplicitComparison", WarnOnImplicitComparison); + Options.store(Opts, "WarnOnLogicalNotComparison", WarnOnLogicalNotComparison); + Options.store(Opts, "StringCompareLikeFunctions", StringCompareLikeFunctions); +} + +void SuspiciousStringCompareCheck::registerMatchers(MatchFinder *Finder) { + // Match relational operators. + const auto ComparisonUnaryOperator = unaryOperator(hasOperatorName("!")); + const auto ComparisonBinaryOperator = + binaryOperator(matchers::isComparisonOperator()); + const auto ComparisonOperator = + expr(anyOf(ComparisonUnaryOperator, ComparisonBinaryOperator)); + + // Add the list of known string compare-like functions and add user-defined + // functions. + std::vector FunctionNames = utils::options::parseStringList( + (llvm::Twine(KnownStringCompareFunctions) + StringCompareLikeFunctions) + .str()); + + // Match a call to a string compare functions. + const auto FunctionCompareDecl = + functionDecl(hasAnyName(std::vector(FunctionNames.begin(), + FunctionNames.end()))) + .bind("decl"); + const auto DirectStringCompareCallExpr = + callExpr(hasDeclaration(FunctionCompareDecl)).bind("call"); + const auto MacroStringCompareCallExpr = conditionalOperator(anyOf( + hasTrueExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)), + hasFalseExpression(ignoringParenImpCasts(DirectStringCompareCallExpr)))); + // The implicit cast is not present in C. + const auto StringCompareCallExpr = ignoringParenImpCasts( + anyOf(DirectStringCompareCallExpr, MacroStringCompareCallExpr)); + + if (WarnOnImplicitComparison) { + // Detect suspicious calls to string compare: + // 'if (strcmp())' -> 'if (strcmp() != 0)' + Finder->addMatcher( + stmt(anyOf(ifStmt(hasCondition(StringCompareCallExpr)), + whileStmt(hasCondition(StringCompareCallExpr)), + doStmt(hasCondition(StringCompareCallExpr)), + forStmt(hasCondition(StringCompareCallExpr)), + binaryOperator( + anyOf(hasOperatorName("&&"), hasOperatorName("||")), + hasEitherOperand(StringCompareCallExpr)))) + .bind("missing-comparison"), + this); + } + + if (WarnOnLogicalNotComparison) { + // Detect suspicious calls to string compared with '!' operator: + // 'if (!strcmp())' -> 'if (strcmp() == 0)' + Finder->addMatcher(unaryOperator(hasOperatorName("!"), + hasUnaryOperand(ignoringParenImpCasts( + StringCompareCallExpr))) + .bind("logical-not-comparison"), + this); + } + + // Detect suspicious cast to an inconsistant type (i.e. not integer type). + Finder->addMatcher( + implicitCastExpr(unless(hasType(isInteger())), + hasSourceExpression(StringCompareCallExpr)) + .bind("invalid-conversion"), + this); + + // Detect suspicious operator with string compare function as operand. + Finder->addMatcher( + binaryOperator( + unless(anyOf(matchers::isComparisonOperator(), hasOperatorName("&&"), + hasOperatorName("||"), hasOperatorName("="))), + hasEitherOperand(StringCompareCallExpr)) + .bind("suspicious-operator"), + this); + + // Detect comparison to invalid constant: 'strcmp() == -1'. + const auto InvalidLiteral = ignoringParenImpCasts( + anyOf(integerLiteral(unless(equals(0))), + unaryOperator( + hasOperatorName("-"), + has(ignoringParenImpCasts(integerLiteral(unless(equals(0)))))), + characterLiteral(), cxxBoolLiteral())); + + Finder->addMatcher(binaryOperator(matchers::isComparisonOperator(), + hasEitherOperand(StringCompareCallExpr), + hasEitherOperand(InvalidLiteral)) + .bind("invalid-comparison"), + this); +} + +void SuspiciousStringCompareCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Decl = Result.Nodes.getNodeAs("decl"); + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Decl != nullptr && Call != nullptr); + + if (Result.Nodes.getNodeAs("missing-comparison")) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Call->getRParenLoc(), 0, Result.Context->getSourceManager(), + getLangOpts()); + + diag(Call->getLocStart(), + "function %0 is called without explicitly comparing result") + << Decl << FixItHint::CreateInsertion(EndLoc, " != 0"); + } + + if (const auto *E = Result.Nodes.getNodeAs("logical-not-comparison")) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Call->getRParenLoc(), 0, Result.Context->getSourceManager(), + getLangOpts()); + SourceLocation NotLoc = E->getLocStart(); + + diag(Call->getLocStart(), + "function %0 is compared using logical not operator") + << Decl << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(NotLoc, NotLoc)) + << FixItHint::CreateInsertion(EndLoc, " == 0"); + } + + if (Result.Nodes.getNodeAs("invalid-comparison")) { + diag(Call->getLocStart(), + "function %0 is compared to a suspicious constant") + << Decl; + } + + if (const auto *BinOp = + Result.Nodes.getNodeAs("suspicious-operator")) { + diag(Call->getLocStart(), "results of function %0 used by operator '%1'") + << Decl << BinOp->getOpcodeStr(); + } + + if (Result.Nodes.getNodeAs("invalid-conversion")) { + diag(Call->getLocStart(), "function %0 has suspicious implicit cast") + << Decl; + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SuspiciousStringCompareCheck.h b/clang-tidy/misc/SuspiciousStringCompareCheck.h new file mode 100644 index 000000000..0e9bd45c5 --- /dev/null +++ b/clang-tidy/misc/SuspiciousStringCompareCheck.h @@ -0,0 +1,40 @@ +//===--- SuspiciousStringCompareCheck.h - clang-tidy-------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_STRING_COMPARE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_STRING_COMPARE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find suspicious calls to string compare functions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-suspicious-string-compare.html +class SuspiciousStringCompareCheck : public ClangTidyCheck { +public: + SuspiciousStringCompareCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool WarnOnImplicitComparison; + const bool WarnOnLogicalNotComparison; + const std::string StringCompareLikeFunctions; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SUSPICIOUS_STRING_COMPARE_H diff --git a/clang-tidy/misc/SwappedArgumentsCheck.cpp b/clang-tidy/misc/SwappedArgumentsCheck.cpp new file mode 100644 index 000000000..e4dc5ca05 --- /dev/null +++ b/clang-tidy/misc/SwappedArgumentsCheck.cpp @@ -0,0 +1,102 @@ +//===--- SwappedArgumentsCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SwappedArgumentsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" +#include "llvm/ADT/SmallPtrSet.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void SwappedArgumentsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("call"), this); +} + +/// \brief Look through lvalue to rvalue and nop casts. This filters out +/// implicit conversions that have no effect on the input but block our view for +/// other implicit casts. +static const Expr *ignoreNoOpCasts(const Expr *E) { + if (auto *Cast = dyn_cast(E)) + if (Cast->getCastKind() == CK_LValueToRValue || + Cast->getCastKind() == CK_NoOp) + return ignoreNoOpCasts(Cast->getSubExpr()); + return E; +} + +/// \brief Restrict the warning to implicit casts that are most likely +/// accidental. User defined or integral conversions fit in this category, +/// lvalue to rvalue or derived to base does not. +static bool isImplicitCastCandidate(const CastExpr *Cast) { + return Cast->getCastKind() == CK_UserDefinedConversion || + Cast->getCastKind() == CK_FloatingToBoolean || + Cast->getCastKind() == CK_FloatingToIntegral || + Cast->getCastKind() == CK_IntegralToBoolean || + Cast->getCastKind() == CK_IntegralToFloating || + Cast->getCastKind() == CK_MemberPointerToBoolean || + Cast->getCastKind() == CK_PointerToBoolean; +} + +void SwappedArgumentsCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Ctx = *Result.Context; + const auto *Call = Result.Nodes.getNodeAs("call"); + + llvm::SmallPtrSet UsedArgs; + for (unsigned I = 1, E = Call->getNumArgs(); I < E; ++I) { + const Expr *LHS = Call->getArg(I - 1); + const Expr *RHS = Call->getArg(I); + + // Only need to check RHS, as LHS has already been covered. We don't want to + // emit two warnings for a single argument. + if (UsedArgs.count(RHS)) + continue; + + const auto *LHSCast = dyn_cast(ignoreNoOpCasts(LHS)); + const auto *RHSCast = dyn_cast(ignoreNoOpCasts(RHS)); + + // Look if this is a potentially swapped argument pair. First look for + // implicit casts. + if (!LHSCast || !RHSCast || !isImplicitCastCandidate(LHSCast) || + !isImplicitCastCandidate(RHSCast)) + continue; + + // If the types that go into the implicit casts match the types of the other + // argument in the declaration there is a high probability that the + // arguments were swapped. + // TODO: We could make use of the edit distance between the argument name + // and the name of the passed variable in addition to this type based + // heuristic. + const Expr *LHSFrom = ignoreNoOpCasts(LHSCast->getSubExpr()); + const Expr *RHSFrom = ignoreNoOpCasts(RHSCast->getSubExpr()); + if (LHS->getType() == RHS->getType() || + LHS->getType() != RHSFrom->getType() || + RHS->getType() != LHSFrom->getType()) + continue; + + // Emit a warning and fix-its that swap the arguments. + diag(Call->getLocStart(), "argument with implicit conversion from %0 " + "to %1 followed by argument converted from " + "%2 to %3, potentially swapped arguments.") + << LHS->getType() << LHSFrom->getType() << RHS->getType() + << RHSFrom->getType() + << tooling::fixit::createReplacement(*LHS, *RHS, Ctx) + << tooling::fixit::createReplacement(*RHS, *LHS, Ctx); + + // Remember that we emitted a warning for this argument. + UsedArgs.insert(RHSCast); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/SwappedArgumentsCheck.h b/clang-tidy/misc/SwappedArgumentsCheck.h new file mode 100644 index 000000000..ed3266195 --- /dev/null +++ b/clang-tidy/misc/SwappedArgumentsCheck.h @@ -0,0 +1,32 @@ +//===--- SwappedArgumentsCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds potentially swapped arguments by looking at implicit conversions. +class SwappedArgumentsCheck : public ClangTidyCheck { +public: + SwappedArgumentsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_SWAPPEDARGUMENTSCHECK_H diff --git a/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp new file mode 100644 index 000000000..00befd2e7 --- /dev/null +++ b/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp @@ -0,0 +1,159 @@ +//===--- 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) { + const char *diagMsgCatchReference = "catch handler catches a pointer value; " + "should throw a non-pointer value and " + "catch by reference instead"; + if (!catchStmt) + return; + auto caughtType = catchStmt->getCaughtType(); + if (caughtType.isNull()) + return; + auto *varDecl = catchStmt->getExceptionDecl(); + if (const auto *PT = caughtType.getCanonicalType()->getAs()) { + // 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()) { + // If it's not a pointer and not a reference then it must be thrown "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..841f13d52 --- /dev/null +++ b/clang-tidy/misc/UnconventionalAssignOperatorCheck.cpp @@ -0,0 +1,91 @@ +//===--- UnconventionalAssignOperatorCheck.cpp - clang-tidy -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnconventionalAssignOperatorCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UnconventionalAssignOperatorCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto HasGoodReturnType = cxxMethodDecl(returns( + lValueReferenceType(pointee(unless(isConstQualified()), + hasDeclaration(equalsBoundNode("class")))))); + + const auto IsSelf = qualType( + anyOf(hasDeclaration(equalsBoundNode("class")), + referenceType(pointee(hasDeclaration(equalsBoundNode("class")))))); + const auto IsAssign = + cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), + hasName("operator="), ofClass(recordDecl().bind("class"))) + .bind("method"); + const auto IsSelfAssign = + cxxMethodDecl(IsAssign, hasParameter(0, parmVarDecl(hasType(IsSelf)))) + .bind("method"); + + Finder->addMatcher( + cxxMethodDecl(IsAssign, unless(HasGoodReturnType)).bind("ReturnType"), + this); + + const auto BadSelf = referenceType( + anyOf(lValueReferenceType(pointee(unless(isConstQualified()))), + rValueReferenceType(pointee(isConstQualified())))); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, + hasParameter(0, parmVarDecl(hasType(BadSelf)))) + .bind("ArgumentType"), + this); + + Finder->addMatcher( + cxxMethodDecl(IsSelfAssign, anyOf(isConst(), isVirtual())).bind("cv"), + this); + + const auto IsBadReturnStatement = returnStmt(unless(has(ignoringParenImpCasts( + unaryOperator(hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())))))); + const auto IsGoodAssign = cxxMethodDecl(IsAssign, HasGoodReturnType); + + Finder->addMatcher(returnStmt(IsBadReturnStatement, forFunction(IsGoodAssign)) + .bind("returnStmt"), + this); +} + +void UnconventionalAssignOperatorCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *RetStmt = Result.Nodes.getNodeAs("returnStmt")) { + diag(RetStmt->getLocStart(), "operator=() should always return '*this'"); + } else { + static const char *const Messages[][2] = { + {"ReturnType", "operator=() should return '%0&'"}, + {"ArgumentType", "operator=() should take '%0 const&', '%0&&' or '%0'"}, + {"cv", "operator=() should not be marked '%1'"}}; + + const auto *Method = Result.Nodes.getNodeAs("method"); + for (const auto &Message : Messages) { + if (Result.Nodes.getNodeAs(Message[0])) + diag(Method->getLocStart(), Message[1]) + << Method->getParent()->getName() + << (Method->isConst() ? "const" : "virtual"); + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnconventionalAssignOperatorCheck.h b/clang-tidy/misc/UnconventionalAssignOperatorCheck.h new file mode 100644 index 000000000..ee91dcaaa --- /dev/null +++ b/clang-tidy/misc/UnconventionalAssignOperatorCheck.h @@ -0,0 +1,42 @@ +//===--- UnconventionalAssignOperatorCheck.h - clang-tidy -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds declarations of assignment operators with the wrong return and/or +/// argument types and definitions with good return type but wrong return +/// statements. +/// +/// * The return type must be `Class&`. +/// * Works with move-assign and assign by value. +/// * Private and deleted operators are ignored. +/// * The operator must always return ``*this``. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unconventional-assign-operator.html +class UnconventionalAssignOperatorCheck : public ClangTidyCheck { +public: + UnconventionalAssignOperatorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_ASSIGNOPERATORSIGNATURECHECK_H diff --git a/clang-tidy/misc/UndelegatedConstructor.cpp b/clang-tidy/misc/UndelegatedConstructor.cpp new file mode 100644 index 000000000..f42f1c5af --- /dev/null +++ b/clang-tidy/misc/UndelegatedConstructor.cpp @@ -0,0 +1,84 @@ +//===--- UndelegatedConstructor.cpp - clang-tidy --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UndelegatedConstructor.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER_P(Stmt, ignoringTemporaryExpr, + ast_matchers::internal::Matcher, InnerMatcher) { + const Stmt *E = &Node; + for (;;) { + // Temporaries with non-trivial dtors. + if (const auto *EWC = dyn_cast(E)) + E = EWC->getSubExpr(); + // Temporaries with zero or more than two ctor arguments. + else if (const auto *BTE = dyn_cast(E)) + E = BTE->getSubExpr(); + // Temporaries with exactly one ctor argument. + else if (const auto *FCE = dyn_cast(E)) + E = FCE->getSubExpr(); + else + break; + } + + return InnerMatcher.matches(*E, Finder, Builder); +} + +// Finds a node if it's a base of an already bound node. +AST_MATCHER_P(CXXRecordDecl, baseOfBoundNode, std::string, ID) { + return Builder->removeBindings( + [&](const ast_matchers::internal::BoundNodesMap &Nodes) { + const auto *Derived = Nodes.getNodeAs(ID); + return Derived != &Node && !Derived->isDerivedFrom(&Node); + }); +} +} // namespace + +void UndelegatedConstructorCheck::registerMatchers(MatchFinder *Finder) { + // We look for calls to constructors of the same type in constructors. To do + // this we have to look through a variety of nodes that occur in the path, + // depending on the type's destructor and the number of arguments on the + // constructor call, this is handled by ignoringTemporaryExpr. Ignore template + // instantiations to reduce the number of duplicated warnings. + // + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + compoundStmt( + hasParent( + cxxConstructorDecl(ofClass(cxxRecordDecl().bind("parent")))), + forEach(ignoringTemporaryExpr( + cxxConstructExpr(hasDeclaration(cxxConstructorDecl(ofClass( + cxxRecordDecl(baseOfBoundNode("parent")))))) + .bind("construct"))), + unless(isInTemplateInstantiation())), + this); +} + +void UndelegatedConstructorCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("construct"); + diag(E->getLocStart(), "did you intend to call a delegated constructor? " + "A temporary object is created here instead"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UndelegatedConstructor.h b/clang-tidy/misc/UndelegatedConstructor.h new file mode 100644 index 000000000..bba36ed49 --- /dev/null +++ b/clang-tidy/misc/UndelegatedConstructor.h @@ -0,0 +1,36 @@ +//===--- UndelegatedConstructor.h - clang-tidy ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds creation of temporary objects in constructors that look like a +/// function call to another constructor of the same class. +/// +/// The user most likely meant to use a delegating constructor or base class +/// initializer. +class UndelegatedConstructorCheck : public ClangTidyCheck { +public: + UndelegatedConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNDELEGATEDCONSTRUCTOR_H diff --git a/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp b/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp new file mode 100644 index 000000000..99758d334 --- /dev/null +++ b/clang-tidy/misc/UniqueptrResetReleaseCheck.cpp @@ -0,0 +1,136 @@ +//===--- UniqueptrResetReleaseCheck.cpp - clang-tidy ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UniqueptrResetReleaseCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UniqueptrResetReleaseCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + Finder->addMatcher( + cxxMemberCallExpr( + on(expr().bind("left")), callee(memberExpr().bind("reset_member")), + callee( + cxxMethodDecl(hasName("reset"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("left_class"))))), + has(ignoringParenImpCasts(cxxMemberCallExpr( + on(expr().bind("right")), + callee(memberExpr().bind("release_member")), + callee(cxxMethodDecl( + hasName("release"), + ofClass(cxxRecordDecl(hasName("::std::unique_ptr"), + decl().bind("right_class"))))))))) + .bind("reset_call"), + this); +} + +namespace { +const Type *getDeleterForUniquePtr(const MatchFinder::MatchResult &Result, + StringRef ID) { + const auto *Class = + Result.Nodes.getNodeAs(ID); + if (!Class) + return nullptr; + auto DeleterArgument = Class->getTemplateArgs()[1]; + if (DeleterArgument.getKind() != TemplateArgument::Type) + return nullptr; + return DeleterArgument.getAsType().getTypePtr(); +} + +bool areDeletersCompatible(const MatchFinder::MatchResult &Result) { + const Type *LeftDeleterType = getDeleterForUniquePtr(Result, "left_class"); + const Type *RightDeleterType = getDeleterForUniquePtr(Result, "right_class"); + + if (LeftDeleterType->getUnqualifiedDesugaredType() == + RightDeleterType->getUnqualifiedDesugaredType()) { + // Same type. We assume they are compatible. + // This check handles the case where the deleters are function pointers. + return true; + } + + const CXXRecordDecl *LeftDeleter = LeftDeleterType->getAsCXXRecordDecl(); + const CXXRecordDecl *RightDeleter = RightDeleterType->getAsCXXRecordDecl(); + if (!LeftDeleter || !RightDeleter) + return false; + + if (LeftDeleter->getCanonicalDecl() == RightDeleter->getCanonicalDecl()) { + // Same class. We assume they are compatible. + return true; + } + + const auto *LeftAsTemplate = + dyn_cast(LeftDeleter); + const auto *RightAsTemplate = + dyn_cast(RightDeleter); + if (LeftAsTemplate && RightAsTemplate && + LeftAsTemplate->getSpecializedTemplate() == + RightAsTemplate->getSpecializedTemplate()) { + // They are different instantiations of the same template. We assume they + // are compatible. + // This handles things like std::default_delete vs. + // std::default_delete. + return true; + } + return false; +} + +} // namespace + +void UniqueptrResetReleaseCheck::check(const MatchFinder::MatchResult &Result) { + if (!areDeletersCompatible(Result)) + return; + + const auto *ResetMember = Result.Nodes.getNodeAs("reset_member"); + const auto *ReleaseMember = + Result.Nodes.getNodeAs("release_member"); + const auto *Right = Result.Nodes.getNodeAs("right"); + const auto *Left = Result.Nodes.getNodeAs("left"); + const auto *ResetCall = + Result.Nodes.getNodeAs("reset_call"); + + std::string LeftText = clang::Lexer::getSourceText( + CharSourceRange::getTokenRange(Left->getSourceRange()), + *Result.SourceManager, getLangOpts()); + std::string RightText = clang::Lexer::getSourceText( + CharSourceRange::getTokenRange(Right->getSourceRange()), + *Result.SourceManager, getLangOpts()); + + if (ResetMember->isArrow()) + LeftText = "*" + LeftText; + if (ReleaseMember->isArrow()) + RightText = "*" + RightText; + std::string DiagText; + // Even if x was rvalue, *x is not rvalue anymore. + if (!Right->isRValue() || ReleaseMember->isArrow()) { + RightText = "std::move(" + RightText + ")"; + DiagText = "prefer ptr1 = std::move(ptr2) over ptr1.reset(ptr2.release())"; + } else { + DiagText = + "prefer ptr = ReturnUnique() over ptr.reset(ReturnUnique().release())"; + } + std::string NewText = LeftText + " = " + RightText; + + diag(ResetMember->getExprLoc(), DiagText) << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ResetCall->getSourceRange()), NewText); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UniqueptrResetReleaseCheck.h b/clang-tidy/misc/UniqueptrResetReleaseCheck.h new file mode 100644 index 000000000..cf18a5a58 --- /dev/null +++ b/clang-tidy/misc/UniqueptrResetReleaseCheck.h @@ -0,0 +1,43 @@ +//===--- UniqueptrResetReleaseCheck.h - clang-tidy --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Find and replace `unique_ptr::reset(release())` with `std::move()`. +/// +/// Example: +/// +/// \code +/// std::unique_ptr x, y; +/// x.reset(y.release()); -> x = std::move(y); +/// \endcode +/// +/// If `y` is already rvalue, `std::move()` is not added. `x` and `y` can also +/// be `std::unique_ptr*`. +class UniqueptrResetReleaseCheck : public ClangTidyCheck { +public: + UniqueptrResetReleaseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNIQUEPTRRESETRELEASECHECK_H diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.cpp b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp new file mode 100644 index 000000000..07165d65d --- /dev/null +++ b/clang-tidy/misc/UnusedAliasDeclsCheck.cpp @@ -0,0 +1,64 @@ +//===--- UnusedAliasDeclsCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedAliasDeclsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UnusedAliasDeclsCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++11; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus11) + return; + + // We cannot do anything about headers (yet), as the alias declarations + // used in one header could be used by some other translation unit. + Finder->addMatcher(namespaceAliasDecl(isExpansionInMainFile()).bind("alias"), + this); + Finder->addMatcher(nestedNameSpecifier().bind("nns"), this); +} + +void UnusedAliasDeclsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *AliasDecl = Result.Nodes.getNodeAs("alias")) { + FoundDecls[AliasDecl] = CharSourceRange::getCharRange( + AliasDecl->getLocStart(), + Lexer::findLocationAfterToken( + AliasDecl->getLocEnd(), tok::semi, *Result.SourceManager, + getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + return; + } + + if (const auto *NestedName = + Result.Nodes.getNodeAs("nns")) { + if (const auto *AliasDecl = NestedName->getAsNamespaceAlias()) { + FoundDecls[AliasDecl] = CharSourceRange(); + } + } +} + +void UnusedAliasDeclsCheck::onEndOfTranslationUnit() { + for (const auto &FoundDecl : FoundDecls) { + if (!FoundDecl.second.isValid()) + continue; + diag(FoundDecl.first->getLocation(), "namespace alias decl %0 is unused") + << FoundDecl.first << FixItHint::CreateRemoval(FoundDecl.second); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedAliasDeclsCheck.h b/clang-tidy/misc/UnusedAliasDeclsCheck.h new file mode 100644 index 000000000..8cce37563 --- /dev/null +++ b/clang-tidy/misc/UnusedAliasDeclsCheck.h @@ -0,0 +1,37 @@ +//===--- UnusedAliasDeclsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds unused namespace alias declarations. +class UnusedAliasDeclsCheck : public ClangTidyCheck { +public: + UnusedAliasDeclsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + llvm::DenseMap FoundDecls; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_ALIAS_DECLS_H diff --git a/clang-tidy/misc/UnusedParametersCheck.cpp b/clang-tidy/misc/UnusedParametersCheck.cpp new file mode 100644 index 000000000..6c60edeed --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.cpp @@ -0,0 +1,126 @@ +//===--- 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/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +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().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)); +} + +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; + + auto DeclRefExpr = + declRefExpr(to(equalsNode(Function)), + unless(hasAncestor(callExpr(callee(equalsNode(Function)))))); + + // Comment out parameter name for non-local functions. + if (Function->isExternallyVisible() || + !Result.SourceManager->isInMainFile(Function->getLocation()) || + !ast_matchers::match(DeclRefExpr, *Result.Context).empty() || + isOverrideMethod(Function)) { + SourceRange RemovalRange(Param->getLocation(), Param->getLocEnd()); + // Note: We always add a space before the '/*' to not accidentally create a + // '*/*' for pointer types, which doesn't start a comment. clang-format will + // clean this up afterwards. + MyDiag << FixItHint::CreateReplacement( + RemovalRange, (Twine(" /*") + Param->getName() + "*/").str()); + return; + } + + // Fix all redeclarations. + for (const FunctionDecl *FD : Function->redecls()) + if (FD->param_size()) + MyDiag << removeParameter(Result, FD, ParamIndex); + + // Fix all call sites. + auto CallMatches = ast_matchers::match( + decl(forEachDescendant( + callExpr(callee(functionDecl(equalsNode(Function)))).bind("x"))), + *Result.Context->getTranslationUnitDecl(), *Result.Context); + for (const auto &Match : CallMatches) + MyDiag << removeArgument(Result, Match.getNodeAs("x"), + ParamIndex); +} + +void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Function = Result.Nodes.getNodeAs("function"); + if (!Function->doesThisDeclarationHaveABody() || + !Function->hasWrittenPrototype() || Function->isTemplateInstantiation()) + return; + if (const auto *Method = dyn_cast(Function)) + if (Method->isLambdaStaticInvoker()) + return; + for (unsigned i = 0, e = Function->getNumParams(); i != e; ++i) { + const auto *Param = Function->getParamDecl(i); + if (Param->isUsed() || Param->isReferenced() || !Param->getDeclName() || + Param->hasAttr()) + continue; + warnOnUnusedParameter(Result, Function, i); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedParametersCheck.h b/clang-tidy/misc/UnusedParametersCheck.h new file mode 100644 index 000000000..334f89715 --- /dev/null +++ b/clang-tidy/misc/UnusedParametersCheck.h @@ -0,0 +1,38 @@ +//===--- 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) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void + warnOnUnusedParameter(const ast_matchers::MatchFinder::MatchResult &Result, + const FunctionDecl *Function, unsigned ParamIndex); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_PARAMETERS_H diff --git a/clang-tidy/misc/UnusedRAIICheck.cpp b/clang-tidy/misc/UnusedRAIICheck.cpp new file mode 100644 index 000000000..e1acfe97c --- /dev/null +++ b/clang-tidy/misc/UnusedRAIICheck.cpp @@ -0,0 +1,94 @@ +//===--- UnusedRAIICheck.cpp - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnusedRAIICheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +namespace { +AST_MATCHER(CXXRecordDecl, hasNonTrivialDestructor) { + // TODO: If the dtor is there but empty we don't want to warn either. + return Node.hasDefinition() && Node.hasNonTrivialDestructor(); +} +} // namespace + +void UnusedRAIICheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Look for temporaries that are constructed in-place and immediately + // destroyed. Look for temporaries created by a functional cast but not for + // those returned from a call. + auto BindTemp = + cxxBindTemporaryExpr(unless(has(ignoringParenImpCasts(callExpr())))) + .bind("temp"); + Finder->addMatcher( + exprWithCleanups(unless(isInTemplateInstantiation()), + hasParent(compoundStmt().bind("compound")), + hasType(cxxRecordDecl(hasNonTrivialDestructor())), + anyOf(has(ignoringParenImpCasts(BindTemp)), + has(ignoringParenImpCasts(cxxFunctionalCastExpr( + has(ignoringParenImpCasts(BindTemp))))))) + .bind("expr"), + this); +} + +void UnusedRAIICheck::check(const MatchFinder::MatchResult &Result) { + const auto *E = Result.Nodes.getNodeAs("expr"); + + // We ignore code expanded from macros to reduce the number of false + // positives. + if (E->getLocStart().isMacroID()) + return; + + // Don't emit a warning for the last statement in the surrounding compund + // statement. + const auto *CS = Result.Nodes.getNodeAs("compound"); + if (E == CS->body_back()) + return; + + // Emit a warning. + auto D = diag(E->getLocStart(), "object destroyed immediately after " + "creation; did you mean to name the object?"); + const char *Replacement = " give_me_a_name"; + + // If this is a default ctor we have to remove the parens or we'll introduce a + // most vexing parse. + const auto *BTE = Result.Nodes.getNodeAs("temp"); + if (const auto *TOE = dyn_cast(BTE->getSubExpr())) + if (TOE->getNumArgs() == 0) { + D << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(TOE->getParenOrBraceRange()), + Replacement); + return; + } + + // Otherwise just suggest adding a name. To find the place to insert the name + // find the first TypeLoc in the children of E, which always points to the + // written type. + auto Matches = + match(expr(hasDescendant(typeLoc().bind("t"))), *E, *Result.Context); + const auto *TL = selectFirst("t", Matches); + D << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(TL->getLocEnd(), 0, *Result.SourceManager, + getLangOpts()), + Replacement); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedRAIICheck.h b/clang-tidy/misc/UnusedRAIICheck.h new file mode 100644 index 000000000..40f44e3dc --- /dev/null +++ b/clang-tidy/misc/UnusedRAIICheck.h @@ -0,0 +1,35 @@ +//===--- UnusedRAIICheck.h - clang-tidy -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds temporaries that look like RAII objects. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unused-raii.html +class UnusedRAIICheck : public ClangTidyCheck { +public: + UnusedRAIICheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSEDRAIICHECK_H diff --git a/clang-tidy/misc/UnusedUsingDeclsCheck.cpp b/clang-tidy/misc/UnusedUsingDeclsCheck.cpp new file mode 100644 index 000000000..09d32346f --- /dev/null +++ b/clang-tidy/misc/UnusedUsingDeclsCheck.cpp @@ -0,0 +1,164 @@ +//===--- 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 (const auto *Using = Result.Nodes.getNodeAs("using")) { + // Ignores using-declarations defined in macros. + if (Using->getLocation().isMacroID()) + return; + + // Ignores using-declarations defined in class definition. + if (isa(Using->getDeclContext())) + return; + + // FIXME: We ignore using-decls defined in function definitions at the + // moment because of false positives caused by ADL and different function + // scopes. + if (isa(Using->getDeclContext())) + return; + + UsingDeclContext Context(Using); + Context.UsingDeclRange = CharSourceRange::getCharRange( + Using->getLocStart(), + Lexer::findLocationAfterToken( + Using->getLocEnd(), tok::semi, *Result.SourceManager, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + for (const auto *UsingShadow : Using->shadows()) { + const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl(); + if (ShouldCheckDecl(TargetDecl)) + Context.UsingTargetDecls.insert(TargetDecl); + } + if (!Context.UsingTargetDecls.empty()) + Contexts.push_back(Context); + return; + } + // Mark using declarations as used by setting FoundDecls' value to zero. As + // the AST is walked in order, usages are only marked after a the + // corresponding using declaration has been found. + // FIXME: This currently doesn't look at whether the type reference is + // actually found with the help of the using declaration. + if (const auto *Used = Result.Nodes.getNodeAs("used")) { + if (const auto *FD = dyn_cast(Used)) { + removeFromFoundDecls(FD->getPrimaryTemplate()); + } else if (const auto *Specialization = + dyn_cast(Used)) { + Used = Specialization->getSpecializedTemplate(); + } + removeFromFoundDecls(Used); + return; + } + + if (const auto *Used = Result.Nodes.getNodeAs("used")) { + // FIXME: Support non-type template parameters. + if (Used->getKind() == TemplateArgument::Template) { + if (const auto *TD = Used->getAsTemplate().getAsTemplateDecl()) + removeFromFoundDecls(TD); + } else if (Used->getKind() == TemplateArgument::Type) { + if (auto *RD = Used->getAsType()->getAsCXXRecordDecl()) + removeFromFoundDecls(RD); + } + return; + } + + if (const auto *Used = Result.Nodes.getNodeAs("used")) { + removeFromFoundDecls(Used->getAsTemplateDecl()); + return; + } + + if (const auto *DRE = Result.Nodes.getNodeAs("used")) { + if (const auto *FD = dyn_cast(DRE->getDecl())) { + if (const auto *FDT = FD->getPrimaryTemplate()) + removeFromFoundDecls(FDT); + else + removeFromFoundDecls(FD); + } else if (const auto *VD = dyn_cast(DRE->getDecl())) { + removeFromFoundDecls(VD); + } else if (const auto *ECD = dyn_cast(DRE->getDecl())) { + removeFromFoundDecls(ECD); + if (const auto *ET = ECD->getType()->getAs()) + removeFromFoundDecls(ET->getDecl()); + } + } + // Check the uninstantiated template function usage. + if (const auto *ULE = Result.Nodes.getNodeAs("used")) { + for (const NamedDecl *ND : ULE->decls()) { + if (const auto *USD = dyn_cast(ND)) + removeFromFoundDecls(USD->getTargetDecl()->getCanonicalDecl()); + } + } +} + +void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) { + if (!D) + return; + // FIXME: Currently, we don't handle the using-decls being used in different + // scopes (such as different namespaces, different functions). Instead of + // giving an incorrect message, we mark all of them as used. + // + // FIXME: Use a more efficient way to find a matching context. + for (auto &Context : Contexts) { + if (Context.UsingTargetDecls.count(D->getCanonicalDecl()) > 0) + Context.IsUsed = true; + } +} + +void UnusedUsingDeclsCheck::onEndOfTranslationUnit() { + for (const auto &Context : Contexts) { + if (!Context.IsUsed) { + diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused") + << Context.FoundUsingDecl + << FixItHint::CreateRemoval(Context.UsingDeclRange); + } + } + Contexts.clear(); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UnusedUsingDeclsCheck.h b/clang-tidy/misc/UnusedUsingDeclsCheck.h new file mode 100644 index 000000000..2a41a8f63 --- /dev/null +++ b/clang-tidy/misc/UnusedUsingDeclsCheck.h @@ -0,0 +1,58 @@ +//===--- UnusedUsingDeclsCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_USING_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_USING_DECLS_H + +#include "../ClangTidy.h" +#include "llvm/ADT/SmallPtrSet.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds unused using declarations. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unused-using-decls.html +class UnusedUsingDeclsCheck : public ClangTidyCheck { +public: + UnusedUsingDeclsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + void removeFromFoundDecls(const Decl *D); + + struct UsingDeclContext { + explicit UsingDeclContext(const UsingDecl *FoundUsingDecl) + : FoundUsingDecl(FoundUsingDecl), IsUsed(false) {} + // A set saves all UsingShadowDecls introduced by a UsingDecl. A UsingDecl + // can introduce multiple UsingShadowDecls in some cases (such as + // overloaded functions). + llvm::SmallPtrSet UsingTargetDecls; + // The original UsingDecl. + const UsingDecl *FoundUsingDecl; + // The source range of the UsingDecl. + CharSourceRange UsingDeclRange; + // Whether the UsingDecl is used. + bool IsUsed; + }; + + std::vector Contexts; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNUSED_USING_DECLS_H diff --git a/clang-tidy/misc/UseAfterMoveCheck.cpp b/clang-tidy/misc/UseAfterMoveCheck.cpp new file mode 100644 index 000000000..471b75063 --- /dev/null +++ b/clang-tidy/misc/UseAfterMoveCheck.cpp @@ -0,0 +1,425 @@ +//===--- UseAfterMoveCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseAfterMoveCheck.h" + +#include "clang/Analysis/CFG.h" +#include "clang/Lex/Lexer.h" + +#include "../utils/ExprSequence.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::utils; + + +namespace clang { +namespace tidy { +namespace misc { + +namespace { + +/// Contains information about a use-after-move. +struct UseAfterMove { + // The DeclRefExpr that constituted the use of the object. + const DeclRefExpr *DeclRef; + + // Is the order in which the move and the use are evaluated undefined? + bool EvaluationOrderUndefined; +}; + +/// Finds uses of a variable after a move (and maintains state required by the +/// various internal helper functions). +class UseAfterMoveFinder { +public: + UseAfterMoveFinder(ASTContext *TheContext); + + // Within the given function body, finds the first use of 'MovedVariable' that + // occurs after 'MovingCall' (the expression that performs the move). If a + // use-after-move is found, writes information about it to 'TheUseAfterMove'. + // Returns whether a use-after-move was found. + bool find(Stmt *FunctionBody, const Expr *MovingCall, + const ValueDecl *MovedVariable, UseAfterMove *TheUseAfterMove); + +private: + bool findInternal(const CFGBlock *Block, const Expr *MovingCall, + const ValueDecl *MovedVariable, + UseAfterMove *TheUseAfterMove); + void getUsesAndReinits(const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallVectorImpl *Uses, + llvm::SmallPtrSetImpl *Reinits); + void getDeclRefs(const CFGBlock *Block, const Decl *MovedVariable, + llvm::SmallPtrSetImpl *DeclRefs); + void getReinits(const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallPtrSetImpl *Stmts, + llvm::SmallPtrSetImpl *DeclRefs); + + ASTContext *Context; + std::unique_ptr Sequence; + std::unique_ptr BlockMap; + llvm::SmallPtrSet Visited; +}; + +} // namespace + + +// Matches nodes that are +// - Part of a decltype argument or class template argument (we check this by +// seeing if they are children of a TypeLoc), or +// - Part of a function template argument (we check this by seeing if they are +// children of a DeclRefExpr that references a function template). +// DeclRefExprs that fulfill these conditions should not be counted as a use or +// move. +static StatementMatcher inDecltypeOrTemplateArg() { + return anyOf(hasAncestor(typeLoc()), + hasAncestor(declRefExpr( + to(functionDecl(ast_matchers::isTemplateInstantiation()))))); +} + +UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext) + : Context(TheContext) {} + +bool UseAfterMoveFinder::find(Stmt *FunctionBody, const Expr *MovingCall, + const ValueDecl *MovedVariable, + UseAfterMove *TheUseAfterMove) { + // Generate the CFG manually instead of through an AnalysisDeclContext because + // it seems the latter can't be used to generate a CFG for the body of a + // labmda. + // + // We include implicit and temporary destructors in the CFG so that + // destructors marked [[noreturn]] are handled correctly in the control flow + // analysis. (These are used in some styles of assertion macros.) + CFG::BuildOptions Options; + Options.AddImplicitDtors = true; + Options.AddTemporaryDtors = true; + std::unique_ptr TheCFG = + CFG::buildCFG(nullptr, FunctionBody, Context, Options); + if (!TheCFG) + return false; + + Sequence.reset(new ExprSequence(TheCFG.get(), Context)); + BlockMap.reset(new StmtToBlockMap(TheCFG.get(), Context)); + Visited.clear(); + + const CFGBlock *Block = BlockMap->blockContainingStmt(MovingCall); + if (!Block) + return false; + + return findInternal(Block, MovingCall, MovedVariable, TheUseAfterMove); +} + +bool UseAfterMoveFinder::findInternal(const CFGBlock *Block, + const Expr *MovingCall, + const ValueDecl *MovedVariable, + UseAfterMove *TheUseAfterMove) { + if (Visited.count(Block)) + return false; + + // Mark the block as visited (except if this is the block containing the + // std::move() and it's being visited the first time). + if (!MovingCall) + Visited.insert(Block); + + // Get all uses and reinits in the block. + llvm::SmallVector Uses; + llvm::SmallPtrSet Reinits; + getUsesAndReinits(Block, MovedVariable, &Uses, &Reinits); + + // Ignore all reinitializations where the move potentially comes after the + // reinit. + llvm::SmallVector ReinitsToDelete; + for (const Stmt *Reinit : Reinits) { + if (MovingCall && Sequence->potentiallyAfter(MovingCall, Reinit)) + ReinitsToDelete.push_back(Reinit); + } + for (const Stmt *Reinit : ReinitsToDelete) { + Reinits.erase(Reinit); + } + + // Find all uses that potentially come after the move. + for (const DeclRefExpr *Use : Uses) { + if (!MovingCall || Sequence->potentiallyAfter(Use, MovingCall)) { + // Does the use have a saving reinit? A reinit is saving if it definitely + // comes before the use, i.e. if there's no potential that the reinit is + // after the use. + bool HaveSavingReinit = false; + for (const Stmt *Reinit : Reinits) { + if (!Sequence->potentiallyAfter(Reinit, Use)) + HaveSavingReinit = true; + } + + if (!HaveSavingReinit) { + TheUseAfterMove->DeclRef = Use; + + // Is this a use-after-move that depends on order of evaluation? + // This is the case if the move potentially comes after the use (and we + // already know that use potentially comes after the move, which taken + // together tells us that the ordering is unclear). + TheUseAfterMove->EvaluationOrderUndefined = + MovingCall != nullptr && + Sequence->potentiallyAfter(MovingCall, Use); + + return true; + } + } + } + + // If the object wasn't reinitialized, call ourselves recursively on all + // successors. + if (Reinits.empty()) { + for (const auto &Succ : Block->succs()) { + if (Succ && findInternal(Succ, nullptr, MovedVariable, TheUseAfterMove)) + return true; + } + } + + return false; +} + +void UseAfterMoveFinder::getUsesAndReinits( + const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallVectorImpl *Uses, + llvm::SmallPtrSetImpl *Reinits) { + llvm::SmallPtrSet DeclRefs; + llvm::SmallPtrSet ReinitDeclRefs; + + getDeclRefs(Block, MovedVariable, &DeclRefs); + getReinits(Block, MovedVariable, Reinits, &ReinitDeclRefs); + + // All references to the variable that aren't reinitializations are uses. + Uses->clear(); + for (const DeclRefExpr *DeclRef : DeclRefs) { + if (!ReinitDeclRefs.count(DeclRef)) + Uses->push_back(DeclRef); + } + + // Sort the uses by their occurrence in the source code. + std::sort(Uses->begin(), Uses->end(), + [](const DeclRefExpr *D1, const DeclRefExpr *D2) { + return D1->getExprLoc() < D2->getExprLoc(); + }); +} + +bool isStandardSmartPointer(const ValueDecl *VD) { + const Type *TheType = VD->getType().getTypePtrOrNull(); + if (!TheType) + return false; + + const CXXRecordDecl *RecordDecl = TheType->getAsCXXRecordDecl(); + if (!RecordDecl) + return false; + + const IdentifierInfo *ID = RecordDecl->getIdentifier(); + if (!ID) + return false; + + StringRef Name = ID->getName(); + if (Name != "unique_ptr" && Name != "shared_ptr" && Name != "weak_ptr") + return false; + + return RecordDecl->getDeclContext()->isStdNamespace(); +} + +void UseAfterMoveFinder::getDeclRefs( + const CFGBlock *Block, const Decl *MovedVariable, + llvm::SmallPtrSetImpl *DeclRefs) { + DeclRefs->clear(); + for (const auto &Elem : *Block) { + Optional S = Elem.getAs(); + if (!S) + continue; + + auto addDeclRefs = [this, Block, + DeclRefs](const ArrayRef Matches) { + for (const auto &Match : Matches) { + const auto *DeclRef = Match.getNodeAs("declref"); + const auto *Operator = Match.getNodeAs("operator"); + if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block) { + // Ignore uses of a standard smart pointer that don't dereference the + // pointer. + if (Operator || !isStandardSmartPointer(DeclRef->getDecl())) { + DeclRefs->insert(DeclRef); + } + } + } + }; + + auto DeclRefMatcher = declRefExpr(hasDeclaration(equalsNode(MovedVariable)), + unless(inDecltypeOrTemplateArg())) + .bind("declref"); + + addDeclRefs(match(findAll(DeclRefMatcher), *S->getStmt(), *Context)); + addDeclRefs(match( + findAll(cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("*"), + hasOverloadedOperatorName("->"), + hasOverloadedOperatorName("[]")), + hasArgument(0, DeclRefMatcher)) + .bind("operator")), + *S->getStmt(), *Context)); + } +} + +void UseAfterMoveFinder::getReinits( + const CFGBlock *Block, const ValueDecl *MovedVariable, + llvm::SmallPtrSetImpl *Stmts, + llvm::SmallPtrSetImpl *DeclRefs) { + auto DeclRefMatcher = + declRefExpr(hasDeclaration(equalsNode(MovedVariable))).bind("declref"); + + auto StandardContainerTypeMatcher = hasType(cxxRecordDecl( + hasAnyName("::std::basic_string", "::std::vector", "::std::deque", + "::std::forward_list", "::std::list", "::std::set", + "::std::map", "::std::multiset", "::std::multimap", + "::std::unordered_set", "::std::unordered_map", + "::std::unordered_multiset", "::std::unordered_multimap"))); + + auto StandardSmartPointerTypeMatcher = hasType(cxxRecordDecl( + hasAnyName("::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr"))); + + // Matches different types of reinitialization. + auto ReinitMatcher = + stmt(anyOf( + // Assignment. In addition to the overloaded assignment operator, + // test for built-in assignment as well, since template functions + // may be instantiated to use std::move() on built-in types. + binaryOperator(hasOperatorName("="), hasLHS(DeclRefMatcher)), + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + hasArgument(0, DeclRefMatcher)), + // Declaration. We treat this as a type of reinitialization too, + // so we don't need to treat it separately. + declStmt(hasDescendant(equalsNode(MovedVariable))), + // clear() and assign() on standard containers. + cxxMemberCallExpr( + on(allOf(DeclRefMatcher, StandardContainerTypeMatcher)), + // To keep the matcher simple, we check for assign() calls + // on all standard containers, even though only vector, + // deque, forward_list and list have assign(). If assign() + // is called on any of the other containers, this will be + // flagged by a compile error anyway. + callee(cxxMethodDecl(hasAnyName("clear", "assign")))), + // reset() on standard smart pointers. + cxxMemberCallExpr( + on(allOf(DeclRefMatcher, StandardSmartPointerTypeMatcher)), + callee(cxxMethodDecl(hasName("reset")))), + // Passing variable to a function as a non-const pointer. + callExpr(forEachArgumentWithParam( + unaryOperator(hasOperatorName("&"), + hasUnaryOperand(DeclRefMatcher)), + unless(parmVarDecl(hasType(pointsTo(isConstQualified())))))), + // Passing variable to a function as a non-const lvalue reference + // (unless that function is std::move()). + callExpr(forEachArgumentWithParam( + DeclRefMatcher, + unless(parmVarDecl(hasType( + references(qualType(isConstQualified())))))), + unless(callee(functionDecl(hasName("::std::move"))))))) + .bind("reinit"); + + Stmts->clear(); + DeclRefs->clear(); + for (const auto &Elem : *Block) { + Optional S = Elem.getAs(); + if (!S) + continue; + + SmallVector Matches = + match(findAll(ReinitMatcher), *S->getStmt(), *Context); + + for (const auto &Match : Matches) { + const auto *TheStmt = Match.getNodeAs("reinit"); + const auto *TheDeclRef = Match.getNodeAs("declref"); + if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) { + Stmts->insert(TheStmt); + + // We count DeclStmts as reinitializations, but they don't have a + // DeclRefExpr associated with them -- so we need to check 'TheDeclRef' + // before adding it to the set. + if (TheDeclRef) + DeclRefs->insert(TheDeclRef); + } + } + } +} + +static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg, + const UseAfterMove &Use, ClangTidyCheck *Check, + ASTContext *Context) { + SourceLocation UseLoc = Use.DeclRef->getExprLoc(); + SourceLocation MoveLoc = MovingCall->getExprLoc(); + + Check->diag(UseLoc, "'%0' used after it was moved") + << MoveArg->getDecl()->getName(); + Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note); + if (Use.EvaluationOrderUndefined) { + Check->diag(UseLoc, + "the use and move are unsequenced, i.e. there is no guarantee " + "about the order in which they are evaluated", + DiagnosticIDs::Note); + } else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) { + Check->diag(UseLoc, + "the use happens in a later loop iteration than the move", + DiagnosticIDs::Note); + } +} + +void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + auto CallMoveMatcher = + callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1), + hasArgument(0, declRefExpr().bind("arg")), + anyOf(hasAncestor(lambdaExpr().bind("containing-lambda")), + hasAncestor(functionDecl().bind("containing-func"))), + unless(inDecltypeOrTemplateArg())) + .bind("call-move"); + + Finder->addMatcher( + // To find the Stmt that we assume performs the actual move, we look for + // the direct ancestor of the std::move() that isn't one of the node + // types ignored by ignoringParenImpCasts(). + stmt(forEach(expr(ignoringParenImpCasts(CallMoveMatcher))), + 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 = CallMove; + + Stmt *FunctionBody = nullptr; + if (ContainingLambda) + FunctionBody = ContainingLambda->getBody(); + else if (ContainingFunc) + FunctionBody = ContainingFunc->getBody(); + else + return; + + // Ignore the std::move if the variable that was passed to it isn't a local + // variable. + if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod()) + return; + + UseAfterMoveFinder finder(Result.Context); + UseAfterMove Use; + if (finder.find(FunctionBody, MovingCall, Arg->getDecl(), &Use)) + emitDiagnostic(MovingCall, Arg, Use, this, Result.Context); +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/UseAfterMoveCheck.h b/clang-tidy/misc/UseAfterMoveCheck.h new file mode 100644 index 000000000..2f6be5be3 --- /dev/null +++ b/clang-tidy/misc/UseAfterMoveCheck.h @@ -0,0 +1,36 @@ +//===--- UseAfterMoveCheck.h - clang-tidy ---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// The check warns if an object is used after it has been moved, without an +/// intervening reinitialization. +/// +/// For details, see the user-facing documentation: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-use-after-move.html +class UseAfterMoveCheck : public ClangTidyCheck { +public: + UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H diff --git a/clang-tidy/misc/VirtualNearMissCheck.cpp b/clang-tidy/misc/VirtualNearMissCheck.cpp new file mode 100644 index 000000000..043540666 --- /dev/null +++ b/clang-tidy/misc/VirtualNearMissCheck.cpp @@ -0,0 +1,274 @@ +//===--- VirtualNearMissCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "VirtualNearMissCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { + return Node.isOverloadedOperator(); +} + +/// Finds out if the given method overrides some method. +static bool isOverrideMethod(const CXXMethodDecl *MD) { + return MD->size_overridden_methods() > 0 || MD->hasAttr(); +} + +/// Checks whether the return types are covariant, according to +/// C++[class.virtual]p7. +/// +/// Similar with clang::Sema::CheckOverridingFunctionReturnType. +/// \returns true if the return types of BaseMD and DerivedMD are covariant. +static bool checkOverridingFunctionReturnType(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + QualType BaseReturnTy = BaseMD->getType() + ->getAs() + ->getReturnType() + .getCanonicalType(); + QualType DerivedReturnTy = DerivedMD->getType() + ->getAs() + ->getReturnType() + .getCanonicalType(); + + if (DerivedReturnTy->isDependentType() || BaseReturnTy->isDependentType()) + return false; + + // Check if return types are identical. + if (Context->hasSameType(DerivedReturnTy, BaseReturnTy)) + return true; + + /// Check if the return types are covariant. + + // Both types must be pointers or references to classes. + if (!(BaseReturnTy->isPointerType() && DerivedReturnTy->isPointerType()) && + !(BaseReturnTy->isReferenceType() && DerivedReturnTy->isReferenceType())) + return false; + + /// BTy is the class type in return type of BaseMD. For example, + /// B* Base::md() + /// While BRD is the declaration of B. + QualType DTy = DerivedReturnTy->getPointeeType().getCanonicalType(); + QualType BTy = BaseReturnTy->getPointeeType().getCanonicalType(); + + const CXXRecordDecl *DRD = DTy->getAsCXXRecordDecl(); + const CXXRecordDecl *BRD = BTy->getAsCXXRecordDecl(); + if (DRD == nullptr || BRD == nullptr) + return false; + + if (!DRD->hasDefinition() || !BRD->hasDefinition()) + return false; + + if (DRD == BRD) + return true; + + if (!Context->hasSameUnqualifiedType(DTy, BTy)) { + // Begin checking whether the conversion from D to B is valid. + CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + + // Check whether D is derived from B, and fill in a CXXBasePaths object. + if (!DRD->isDerivedFrom(BRD, Paths)) + return false; + + // Check ambiguity. + if (Paths.isAmbiguous(Context->getCanonicalType(BTy).getUnqualifiedType())) + return false; + + // Check accessibility. + // FIXME: We currently only support checking if B is accessible base class + // of D, or D is the same class which DerivedMD is in. + bool IsItself = + DRD->getCanonicalDecl() == DerivedMD->getParent()->getCanonicalDecl(); + bool HasPublicAccess = false; + for (const auto &Path : Paths) { + if (Path.Access == AS_public) + HasPublicAccess = true; + } + if (!HasPublicAccess && !IsItself) + return false; + // End checking conversion from D to B. + } + + // Both pointers or references should have the same cv-qualification. + if (DerivedReturnTy.getLocalCVRQualifiers() != + BaseReturnTy.getLocalCVRQualifiers()) + return false; + + // The class type D should have the same cv-qualification as or less + // cv-qualification than the class type B. + if (DTy.isMoreQualifiedThan(BTy)) + return false; + + return true; +} + +/// \returns decayed type for arrays and functions. +static QualType getDecayedType(QualType Type) { + if (const auto *Decayed = Type->getAs()) + return Decayed->getDecayedType(); + return Type; +} + +/// \returns true if the param types are the same. +static bool checkParamTypes(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + unsigned NumParamA = BaseMD->getNumParams(); + unsigned NumParamB = DerivedMD->getNumParams(); + if (NumParamA != NumParamB) + return false; + + for (unsigned I = 0; I < NumParamA; I++) { + if (getDecayedType(BaseMD->getParamDecl(I)->getType().getCanonicalType()) != + getDecayedType( + DerivedMD->getParamDecl(I)->getType().getCanonicalType())) + return false; + } + return true; +} + +/// \returns true if derived method can override base method except for the +/// name. +static bool checkOverrideWithoutName(const ASTContext *Context, + const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + if (BaseMD->isStatic() != DerivedMD->isStatic()) + return false; + + if (BaseMD->getType() == DerivedMD->getType()) + return true; + + // Now the function types are not identical. Then check if the return types + // are covariant and if the param types are the same. + if (!checkOverridingFunctionReturnType(Context, BaseMD, DerivedMD)) + return false; + return checkParamTypes(BaseMD, DerivedMD); +} + +/// Check whether BaseMD overrides DerivedMD. +/// +/// Prerequisite: the class which BaseMD is in should be a base class of that +/// DerivedMD is in. +static bool checkOverrideByDerivedMethod(const CXXMethodDecl *BaseMD, + const CXXMethodDecl *DerivedMD) { + for (CXXMethodDecl::method_iterator I = DerivedMD->begin_overridden_methods(), + E = DerivedMD->end_overridden_methods(); + I != E; ++I) { + const CXXMethodDecl *OverriddenMD = *I; + if (BaseMD->getCanonicalDecl() == OverriddenMD->getCanonicalDecl()) + return true; + } + + return false; +} + +bool VirtualNearMissCheck::isPossibleToBeOverridden( + const CXXMethodDecl *BaseMD) { + auto Iter = PossibleMap.find(BaseMD); + if (Iter != PossibleMap.end()) + return Iter->second; + + bool IsPossible = !BaseMD->isImplicit() && !isa(BaseMD) && + !isa(BaseMD) && BaseMD->isVirtual() && + !BaseMD->isOverloadedOperator() && + !isa(BaseMD); + PossibleMap[BaseMD] = IsPossible; + return IsPossible; +} + +bool VirtualNearMissCheck::isOverriddenByDerivedClass( + const CXXMethodDecl *BaseMD, const CXXRecordDecl *DerivedRD) { + auto Key = std::make_pair(BaseMD, DerivedRD); + auto Iter = OverriddenMap.find(Key); + if (Iter != OverriddenMap.end()) + return Iter->second; + + bool IsOverridden = false; + for (const CXXMethodDecl *DerivedMD : DerivedRD->methods()) { + if (!isOverrideMethod(DerivedMD)) + continue; + + if (checkOverrideByDerivedMethod(BaseMD, DerivedMD)) { + IsOverridden = true; + break; + } + } + OverriddenMap[Key] = IsOverridden; + return IsOverridden; +} + +void VirtualNearMissCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxMethodDecl( + unless(anyOf(isOverride(), isImplicit(), cxxConstructorDecl(), + cxxDestructorDecl(), cxxConversionDecl(), isStatic(), + isOverloadedOperator()))) + .bind("method"), + this); +} + +void VirtualNearMissCheck::check(const MatchFinder::MatchResult &Result) { + const auto *DerivedMD = Result.Nodes.getNodeAs("method"); + assert(DerivedMD); + + const ASTContext *Context = Result.Context; + + const auto *DerivedRD = DerivedMD->getParent()->getDefinition(); + assert(DerivedRD); + + for (const auto &BaseSpec : DerivedRD->bases()) { + if (const auto *BaseRD = BaseSpec.getType()->getAsCXXRecordDecl()) { + for (const auto *BaseMD : BaseRD->methods()) { + if (!isPossibleToBeOverridden(BaseMD)) + continue; + + if (isOverriddenByDerivedClass(BaseMD, DerivedRD)) + continue; + + unsigned EditDistance = + BaseMD->getName().edit_distance(DerivedMD->getName()); + if (EditDistance > 0 && EditDistance <= EditDistanceThreshold) { + if (checkOverrideWithoutName(Context, BaseMD, DerivedMD)) { + // A "virtual near miss" is found. + auto Range = CharSourceRange::getTokenRange( + SourceRange(DerivedMD->getLocation())); + + bool ApplyFix = !BaseMD->isTemplateInstantiation() && + !DerivedMD->isTemplateInstantiation(); + auto Diag = + diag(DerivedMD->getLocStart(), + "method '%0' has a similar name and the same signature as " + "virtual method '%1'; did you mean to override it?") + << DerivedMD->getQualifiedNameAsString() + << BaseMD->getQualifiedNameAsString(); + if (ApplyFix) + Diag << FixItHint::CreateReplacement(Range, BaseMD->getName()); + } + } + } + } + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/misc/VirtualNearMissCheck.h b/clang-tidy/misc/VirtualNearMissCheck.h new file mode 100644 index 000000000..4e108f22c --- /dev/null +++ b/clang-tidy/misc/VirtualNearMissCheck.h @@ -0,0 +1,65 @@ +//===--- VirtualNearMissCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// \brief Checks for near miss of virtual methods. +/// +/// For a method in a derived class, this check looks for virtual method with a +/// very similar name and an identical signature defined in a base class. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-virtual-near-miss.html +class VirtualNearMissCheck : public ClangTidyCheck { +public: + VirtualNearMissCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Check if the given method is possible to be overridden by some other + /// method. Operators and destructors are excluded. + /// + /// Results are memoized in PossibleMap. + bool isPossibleToBeOverridden(const CXXMethodDecl *BaseMD); + + /// Check if the given base method is overridden by some methods in the given + /// derived class. + /// + /// Results are memoized in OverriddenMap. + bool isOverriddenByDerivedClass(const CXXMethodDecl *BaseMD, + const CXXRecordDecl *DerivedRD); + + /// Key: the unique ID of a method. + /// Value: whether the method is possible to be overridden. + std::map PossibleMap; + + /// Key: + /// Value: whether the base method is overridden by some method in the derived + /// class. + std::map, bool> + OverriddenMap; + + const unsigned EditDistanceThreshold = 1; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_VIRTUAL_NEAR_MISS_H diff --git a/clang-tidy/modernize/AvoidBindCheck.cpp b/clang-tidy/modernize/AvoidBindCheck.cpp new file mode 100644 index 000000000..7cdbb66f4 --- /dev/null +++ b/clang-tidy/modernize/AvoidBindCheck.cpp @@ -0,0 +1,181 @@ +//===--- AvoidBindCheck.cpp - clang-tidy-----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AvoidBindCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { + +enum BindArgumentKind { BK_Temporary, BK_Placeholder, BK_CallExpr, BK_Other }; + +struct BindArgument { + StringRef Tokens; + BindArgumentKind Kind = BK_Other; + size_t PlaceHolderIndex = 0; +}; + +} // end namespace + +static SmallVector +buildBindArguments(const MatchFinder::MatchResult &Result, const CallExpr *C) { + SmallVector BindArguments; + llvm::Regex MatchPlaceholder("^_([0-9]+)$"); + + // Start at index 1 as first argument to bind is the function name. + for (size_t I = 1, ArgCount = C->getNumArgs(); I < ArgCount; ++I) { + const Expr *E = C->getArg(I); + BindArgument B; + if (const auto *M = dyn_cast(E)) { + const auto *TE = M->GetTemporaryExpr(); + B.Kind = isa(TE) ? BK_CallExpr : BK_Temporary; + } + + B.Tokens = Lexer::getSourceText( + CharSourceRange::getTokenRange(E->getLocStart(), E->getLocEnd()), + *Result.SourceManager, Result.Context->getLangOpts()); + + SmallVector Matches; + if (B.Kind == BK_Other && MatchPlaceholder.match(B.Tokens, &Matches)) { + B.Kind = BK_Placeholder; + B.PlaceHolderIndex = std::stoi(Matches[1]); + } + BindArguments.push_back(B); + } + return BindArguments; +} + +static void addPlaceholderArgs(const ArrayRef Args, + llvm::raw_ostream &Stream) { + auto MaxPlaceholderIt = + std::max_element(Args.begin(), Args.end(), + [](const BindArgument &B1, const BindArgument &B2) { + return B1.PlaceHolderIndex < B2.PlaceHolderIndex; + }); + + // Placeholders (if present) have index 1 or greater. + if (MaxPlaceholderIt == Args.end() || MaxPlaceholderIt->PlaceHolderIndex == 0) + return; + + size_t PlaceholderCount = MaxPlaceholderIt->PlaceHolderIndex; + Stream << "("; + StringRef Delimiter = ""; + for (size_t I = 1; I <= PlaceholderCount; ++I) { + Stream << Delimiter << "auto && arg" << I; + Delimiter = ", "; + } + Stream << ")"; +} + +static void addFunctionCallArgs(const ArrayRef Args, + llvm::raw_ostream &Stream) { + StringRef Delimiter = ""; + for (const auto &B : Args) { + if (B.PlaceHolderIndex) + Stream << Delimiter << "arg" << B.PlaceHolderIndex; + else + Stream << Delimiter << B.Tokens; + Delimiter = ", "; + } +} + +static bool isPlaceHolderIndexRepeated(const ArrayRef Args) { + llvm::SmallSet PlaceHolderIndices; + for (const BindArgument &B : Args) { + if (B.PlaceHolderIndex) { + if (!PlaceHolderIndices.insert(B.PlaceHolderIndex).second) + return true; + } + } + return false; +} + +void AvoidBindCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus14) // Need C++14 for generic lambdas. + return; + + Finder->addMatcher( + callExpr( + callee(namedDecl(hasName("::std::bind"))), + hasArgument(0, declRefExpr(to(functionDecl().bind("f"))).bind("ref"))) + .bind("bind"), + this); +} + +void AvoidBindCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("bind"); + auto Diag = diag(MatchedDecl->getLocStart(), "prefer a lambda to std::bind"); + + const auto Args = buildBindArguments(Result, MatchedDecl); + + // Do not attempt to create fixits for nested call expressions. + // FIXME: Create lambda capture variables to capture output of calls. + // NOTE: Supporting nested std::bind will be more difficult due to placeholder + // sharing between outer and inner std:bind invocations. + if (llvm::any_of(Args, + [](const BindArgument &B) { return B.Kind == BK_CallExpr; })) + return; + + // Do not attempt to create fixits when placeholders are reused. + // Unused placeholders are supported by requiring C++14 generic lambdas. + // FIXME: Support this case by deducing the common type. + if (isPlaceHolderIndexRepeated(Args)) + return; + + const auto *F = Result.Nodes.getNodeAs("f"); + + // std::bind can support argument count mismatch between its arguments and the + // bound function's arguments. Do not attempt to generate a fixit for such + // cases. + // FIXME: Support this case by creating unused lambda capture variables. + if (F->getNumParams() != Args.size()) + return; + + std::string Buffer; + llvm::raw_string_ostream Stream(Buffer); + + bool HasCapturedArgument = llvm::any_of( + Args, [](const BindArgument &B) { return B.Kind == BK_Other; }); + const auto *Ref = Result.Nodes.getNodeAs("ref"); + Stream << "[" << (HasCapturedArgument ? "=" : "") << "]"; + addPlaceholderArgs(Args, Stream); + Stream << " { return "; + Ref->printPretty(Stream, nullptr, Result.Context->getPrintingPolicy()); + Stream << "("; + addFunctionCallArgs(Args, Stream); + Stream << "); };"; + + Diag << FixItHint::CreateReplacement(MatchedDecl->getSourceRange(), + Stream.str()); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/AvoidBindCheck.h b/clang-tidy/modernize/AvoidBindCheck.h new file mode 100644 index 000000000..5ae0241f5 --- /dev/null +++ b/clang-tidy/modernize/AvoidBindCheck.h @@ -0,0 +1,36 @@ +//===--- AvoidBindCheck.h - clang-tidy---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace simple uses of std::bind with a lambda. +/// +/// FIXME: Add support for function references and member function references. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-avoid-std-bind.html +class AvoidBindCheck : public ClangTidyCheck { +public: + AvoidBindCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOID_BIND_H diff --git a/clang-tidy/modernize/CMakeLists.txt b/clang-tidy/modernize/CMakeLists.txt new file mode 100644 index 000000000..22c7d8d5b --- /dev/null +++ b/clang-tidy/modernize/CMakeLists.txt @@ -0,0 +1,36 @@ +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 + ShrinkToFitCheck.cpp + UseAutoCheck.cpp + UseBoolLiteralsCheck.cpp + UseDefaultMemberInitCheck.cpp + UseEmplaceCheck.cpp + UseEqualsDefaultCheck.cpp + UseEqualsDeleteCheck.cpp + UseNullptrCheck.cpp + UseOverrideCheck.cpp + UseTransparentFunctorsCheck.cpp + UseUsingCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyReadabilityModule + clangTidyUtils + ) diff --git a/clang-tidy/modernize/DeprecatedHeadersCheck.cpp b/clang-tidy/modernize/DeprecatedHeadersCheck.cpp new file mode 100644 index 000000000..ea5943e97 --- /dev/null +++ b/clang-tidy/modernize/DeprecatedHeadersCheck.cpp @@ -0,0 +1,123 @@ +//===--- DeprecatedHeadersCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DeprecatedHeadersCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringSet.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { +class IncludeModernizePPCallbacks : public PPCallbacks { +public: + explicit IncludeModernizePPCallbacks(ClangTidyCheck &Check, + LangOptions LangOpts); + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported) override; + +private: + ClangTidyCheck &Check; + LangOptions LangOpts; + llvm::StringMap CStyledHeaderToCxx; + llvm::StringSet<> DeleteHeaders; +}; +} // namespace + +void DeprecatedHeadersCheck::registerPPCallbacks(CompilerInstance &Compiler) { + if (this->getLangOpts().CPlusPlus) { + Compiler.getPreprocessor().addPPCallbacks( + ::llvm::make_unique(*this, + this->getLangOpts())); + } +} + +IncludeModernizePPCallbacks::IncludeModernizePPCallbacks(ClangTidyCheck &Check, + LangOptions LangOpts) + : Check(Check), LangOpts(LangOpts) { + for (const auto &KeyValue : + std::vector>( + {{"assert.h", "cassert"}, + {"complex.h", "complex"}, + {"ctype.h", "cctype"}, + {"errno.h", "cerrno"}, + {"float.h", "cfloat"}, + {"limits.h", "climits"}, + {"locale.h", "clocale"}, + {"math.h", "cmath"}, + {"setjmp.h", "csetjmp"}, + {"signal.h", "csignal"}, + {"stdarg.h", "cstdarg"}, + {"stddef.h", "cstddef"}, + {"stdio.h", "cstdio"}, + {"stdlib.h", "cstdlib"}, + {"string.h", "cstring"}, + {"time.h", "ctime"}, + {"wchar.h", "cwchar"}, + {"wctype.h", "cwctype"}})) { + CStyledHeaderToCxx.insert(KeyValue); + } + // Add C++ 11 headers. + if (LangOpts.CPlusPlus11) { + for (const auto &KeyValue : + std::vector>( + {{"fenv.h", "cfenv"}, + {"stdint.h", "cstdint"}, + {"inttypes.h", "cinttypes"}, + {"tgmath.h", "ctgmath"}, + {"uchar.h", "cuchar"}})) { + CStyledHeaderToCxx.insert(KeyValue); + } + } + for (const auto &Key : + std::vector({"stdalign.h", "stdbool.h", "iso646.h"})) { + DeleteHeaders.insert(Key); + } +} + +void IncludeModernizePPCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported) { + // FIXME: Take care of library symbols from the global namespace. + // + // Reasonable options for the check: + // + // 1. Insert std prefix for every such symbol occurrence. + // 2. Insert `using namespace std;` to the beginning of TU. + // 3. Do nothing and let the user deal with the migration himself. + if (CStyledHeaderToCxx.count(FileName) != 0) { + std::string Replacement = + (llvm::Twine("<") + CStyledHeaderToCxx[FileName] + ">").str(); + Check.diag(FilenameRange.getBegin(), "inclusion of deprecated C++ header " + "'%0'; consider using '%1' instead") + << FileName << CStyledHeaderToCxx[FileName] + << FixItHint::CreateReplacement(FilenameRange.getAsRange(), + Replacement); + } else if (DeleteHeaders.count(FileName) != 0) { + Check.diag(FilenameRange.getBegin(), + "including '%0' has no effect in C++; consider removing it") + << FileName << FixItHint::CreateRemoval( + SourceRange(HashLoc, FilenameRange.getEnd())); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/DeprecatedHeadersCheck.h b/clang-tidy/modernize/DeprecatedHeadersCheck.h new file mode 100644 index 000000000..ee7254e33 --- /dev/null +++ b/clang-tidy/modernize/DeprecatedHeadersCheck.h @@ -0,0 +1,47 @@ +//===--- DeprecatedHeadersCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_C_HEADERS_TO_CXX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_C_HEADERS_TO_CXX_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check replaces deprecated C library headers with their C++ STL +/// alternatives. +/// +/// Before: +/// ~~~{.cpp} +/// #include +/// ~~~ +/// +/// After: +/// ~~~{.cpp} +/// #include +/// ~~~ +/// +/// Example: `` => `` +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-deprecated-headers.html +class DeprecatedHeadersCheck : public ClangTidyCheck { +public: + DeprecatedHeadersCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(CompilerInstance &Compiler) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_C_HEADERS_TO_CXX_H diff --git a/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tidy/modernize/LoopConvertCheck.cpp new file mode 100644 index 000000000..ac0bceb18 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.cpp @@ -0,0 +1,918 @@ +//===--- LoopConvertCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LoopConvertCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Casting.h" +#include +#include +#include + +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char LoopNameArray[] = "forLoopArray"; +static const char LoopNameIterator[] = "forLoopIterator"; +static const char LoopNamePseudoArray[] = "forLoopPseudoArray"; +static const char ConditionBoundName[] = "conditionBound"; +static const char ConditionVarName[] = "conditionVar"; +static const char IncrementVarName[] = "incrementVar"; +static const char InitVarName[] = "initVar"; +static const char BeginCallName[] = "beginCall"; +static const char EndCallName[] = "endCall"; +static const char ConditionEndVarName[] = "conditionEndVar"; +static const char EndVarName[] = "endVar"; +static const char DerefByValueResultName[] = "derefByValueResult"; +static const char DerefByRefResultName[] = "derefByRefResult"; + +// shared matchers +static const TypeMatcher AnyType = anything(); + +static const StatementMatcher IntegerComparisonMatcher = + expr(ignoringParenImpCasts( + declRefExpr(to(varDecl(hasType(isInteger())).bind(ConditionVarName))))); + +static const DeclarationMatcher InitToZeroMatcher = + varDecl(hasInitializer(ignoringParenImpCasts(integerLiteral(equals(0))))) + .bind(InitVarName); + +static const StatementMatcher IncrementVarMatcher = + declRefExpr(to(varDecl(hasType(isInteger())).bind(IncrementVarName))); + +/// \brief The matcher for loops over arrays. +/// +/// In this general example, assuming 'j' and 'k' are of integral type: +/// \code +/// for (int i = 0; j < 3 + 2; ++k) { ... } +/// \endcode +/// The following string identifiers are bound to these parts of the AST: +/// ConditionVarName: 'j' (as a VarDecl) +/// ConditionBoundName: '3 + 2' (as an Expr) +/// InitVarName: 'i' (as a VarDecl) +/// IncrementVarName: 'k' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// +/// Client code will need to make sure that: +/// - The three index variables identified by the matcher are the same +/// VarDecl. +/// - The index variable is only used as an array index. +/// - All arrays indexed by the loop are the same. +StatementMatcher makeArrayLoopMatcher() { + StatementMatcher ArrayBoundMatcher = + expr(hasType(isInteger())).bind(ConditionBoundName); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit(declStmt(hasSingleDecl(InitToZeroMatcher))), + hasCondition(anyOf( + binaryOperator(hasOperatorName("<"), + hasLHS(IntegerComparisonMatcher), + hasRHS(ArrayBoundMatcher)), + binaryOperator(hasOperatorName(">"), hasLHS(ArrayBoundMatcher), + hasRHS(IntegerComparisonMatcher)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(IncrementVarMatcher)))) + .bind(LoopNameArray); +} + +/// \brief The matcher used for iterator-based for loops. +/// +/// This matcher is more flexible than array-based loops. It will match +/// catch loops of the following textual forms (regardless of whether the +/// iterator type is actually a pointer type or a class type): +/// +/// Assuming f, g, and h are of type containerType::iterator, +/// \code +/// for (containerType::iterator it = container.begin(), +/// e = createIterator(); f != g; ++h) { ... } +/// for (containerType::iterator it = container.begin(); +/// f != anotherContainer.end(); ++h) { ... } +/// \endcode +/// The following string identifiers are bound to the parts of the AST: +/// InitVarName: 'it' (as a VarDecl) +/// ConditionVarName: 'f' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// In the first example only: +/// EndVarName: 'e' (as a VarDecl) +/// ConditionEndVarName: 'g' (as a VarDecl) +/// In the second example only: +/// EndCallName: 'container.end()' (as a CXXMemberCallExpr) +/// +/// Client code will need to make sure that: +/// - The iterator variables 'it', 'f', and 'h' are the same. +/// - The two containers on which 'begin' and 'end' are called are the same. +/// - If the end iterator variable 'g' is defined, it is the same as 'f'. +StatementMatcher makeIteratorLoopMatcher() { + StatementMatcher BeginCallMatcher = + cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("begin"), hasName("cbegin"))))) + .bind(BeginCallName); + + DeclarationMatcher InitDeclMatcher = + varDecl(hasInitializer(anyOf(ignoringParenImpCasts(BeginCallMatcher), + materializeTemporaryExpr( + ignoringParenImpCasts(BeginCallMatcher)), + hasDescendant(BeginCallMatcher)))) + .bind(InitVarName); + + DeclarationMatcher EndDeclMatcher = + varDecl(hasInitializer(anything())).bind(EndVarName); + + StatementMatcher EndCallMatcher = cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("end"), hasName("cend"))))); + + StatementMatcher IteratorBoundMatcher = + expr(anyOf(ignoringParenImpCasts( + declRefExpr(to(varDecl().bind(ConditionEndVarName)))), + ignoringParenImpCasts(expr(EndCallMatcher).bind(EndCallName)), + materializeTemporaryExpr(ignoringParenImpCasts( + expr(EndCallMatcher).bind(EndCallName))))); + + StatementMatcher IteratorComparisonMatcher = expr( + ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ConditionVarName))))); + + auto OverloadedNEQMatcher = ignoringImplicit( + cxxOperatorCallExpr(hasOverloadedOperatorName("!="), argumentCountIs(2), + hasArgument(0, IteratorComparisonMatcher), + hasArgument(1, IteratorBoundMatcher))); + + // This matcher tests that a declaration is a CXXRecordDecl that has an + // overloaded operator*(). If the operator*() returns by value instead of by + // reference then the return type is tagged with DerefByValueResultName. + internal::Matcher TestDerefReturnsByValue = + hasType(cxxRecordDecl(hasMethod(allOf( + hasOverloadedOperatorName("*"), + anyOf( + // Tag the return type if it's by value. + returns(qualType(unless(hasCanonicalType(referenceType()))) + .bind(DerefByValueResultName)), + returns( + // Skip loops where the iterator's operator* returns an + // rvalue reference. This is just weird. + qualType(unless(hasCanonicalType(rValueReferenceType()))) + .bind(DerefByRefResultName))))))); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit(anyOf(declStmt(declCountIs(2), + containsDeclaration(0, InitDeclMatcher), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(InitDeclMatcher)))), + hasCondition( + anyOf(binaryOperator(hasOperatorName("!="), + hasLHS(IteratorComparisonMatcher), + hasRHS(IteratorBoundMatcher)), + binaryOperator(hasOperatorName("!="), + hasLHS(IteratorBoundMatcher), + hasRHS(IteratorComparisonMatcher)), + OverloadedNEQMatcher)), + hasIncrement(anyOf( + unaryOperator(hasOperatorName("++"), + hasUnaryOperand(declRefExpr( + to(varDecl(hasType(pointsTo(AnyType))) + .bind(IncrementVarName))))), + cxxOperatorCallExpr( + hasOverloadedOperatorName("++"), + hasArgument( + 0, declRefExpr(to(varDecl(TestDerefReturnsByValue) + .bind(IncrementVarName)))))))) + .bind(LoopNameIterator); +} + +/// \brief The matcher used for array-like containers (pseudoarrays). +/// +/// This matcher is more flexible than array-based loops. It will match +/// loops of the following textual forms (regardless of whether the +/// iterator type is actually a pointer type or a class type): +/// +/// Assuming f, g, and h are of type containerType::iterator, +/// \code +/// for (int i = 0, j = container.size(); f < g; ++h) { ... } +/// for (int i = 0; f < container.size(); ++h) { ... } +/// \endcode +/// The following string identifiers are bound to the parts of the AST: +/// InitVarName: 'i' (as a VarDecl) +/// ConditionVarName: 'f' (as a VarDecl) +/// LoopName: The entire for loop (as a ForStmt) +/// In the first example only: +/// EndVarName: 'j' (as a VarDecl) +/// ConditionEndVarName: 'g' (as a VarDecl) +/// In the second example only: +/// EndCallName: 'container.size()' (as a CXXMemberCallExpr) +/// +/// Client code will need to make sure that: +/// - The index variables 'i', 'f', and 'h' are the same. +/// - The containers on which 'size()' is called is the container indexed. +/// - The index variable is only used in overloaded operator[] or +/// container.at(). +/// - If the end iterator variable 'g' is defined, it is the same as 'j'. +/// - The container's iterators would not be invalidated during the loop. +StatementMatcher makePseudoArrayLoopMatcher() { + // Test that the incoming type has a record declaration that has methods + // called 'begin' and 'end'. If the incoming type is const, then make sure + // these methods are also marked const. + // + // FIXME: To be completely thorough this matcher should also ensure the + // return type of begin/end is an iterator that dereferences to the same as + // what operator[] or at() returns. Such a test isn't likely to fail except + // for pathological cases. + // + // FIXME: Also, a record doesn't necessarily need begin() and end(). Free + // functions called begin() and end() taking the container as an argument + // are also allowed. + TypeMatcher RecordWithBeginEnd = qualType(anyOf( + qualType(isConstQualified(), + hasDeclaration(cxxRecordDecl( + hasMethod(cxxMethodDecl(hasName("begin"), isConst())), + hasMethod(cxxMethodDecl(hasName("end"), + isConst())))) // hasDeclaration + ), // qualType + qualType( + unless(isConstQualified()), + hasDeclaration(cxxRecordDecl(hasMethod(hasName("begin")), + hasMethod(hasName("end"))))) // qualType + )); + + StatementMatcher SizeCallMatcher = cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl(anyOf(hasName("size"), hasName("length")))), + on(anyOf(hasType(pointsTo(RecordWithBeginEnd)), + hasType(RecordWithBeginEnd)))); + + StatementMatcher EndInitMatcher = + expr(anyOf(ignoringParenImpCasts(expr(SizeCallMatcher).bind(EndCallName)), + explicitCastExpr(hasSourceExpression(ignoringParenImpCasts( + expr(SizeCallMatcher).bind(EndCallName)))))); + + DeclarationMatcher EndDeclMatcher = + varDecl(hasInitializer(EndInitMatcher)).bind(EndVarName); + + StatementMatcher IndexBoundMatcher = + expr(anyOf(ignoringParenImpCasts(declRefExpr(to( + varDecl(hasType(isInteger())).bind(ConditionEndVarName)))), + EndInitMatcher)); + + return forStmt( + unless(isInTemplateInstantiation()), + hasLoopInit( + anyOf(declStmt(declCountIs(2), + containsDeclaration(0, InitToZeroMatcher), + containsDeclaration(1, EndDeclMatcher)), + declStmt(hasSingleDecl(InitToZeroMatcher)))), + hasCondition(anyOf( + binaryOperator(hasOperatorName("<"), + hasLHS(IntegerComparisonMatcher), + hasRHS(IndexBoundMatcher)), + binaryOperator(hasOperatorName(">"), hasLHS(IndexBoundMatcher), + hasRHS(IntegerComparisonMatcher)))), + hasIncrement(unaryOperator(hasOperatorName("++"), + hasUnaryOperand(IncrementVarMatcher)))) + .bind(LoopNamePseudoArray); +} + +/// \brief Determine whether Init appears to be an initializing an iterator. +/// +/// If it is, returns the object whose begin() or end() method is called, and +/// the output parameter isArrow is set to indicate whether the initialization +/// is called via . or ->. +static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin, + bool *IsArrow) { + // FIXME: Maybe allow declaration/initialization outside of the for loop. + const auto *TheCall = + dyn_cast_or_null(digThroughConstructors(Init)); + if (!TheCall || TheCall->getNumArgs() != 0) + return nullptr; + + const auto *Member = dyn_cast(TheCall->getCallee()); + if (!Member) + return nullptr; + StringRef Name = Member->getMemberDecl()->getName(); + StringRef TargetName = IsBegin ? "begin" : "end"; + StringRef ConstTargetName = IsBegin ? "cbegin" : "cend"; + if (Name != TargetName && Name != ConstTargetName) + return nullptr; + + const Expr *SourceExpr = Member->getBase(); + if (!SourceExpr) + return nullptr; + + *IsArrow = Member->isArrow(); + return SourceExpr; +} + +/// \brief Determines the container whose begin() and end() functions are called +/// for an iterator-based loop. +/// +/// BeginExpr must be a member call to a function named "begin()", and EndExpr +/// must be a member. +static const Expr *findContainer(ASTContext *Context, const Expr *BeginExpr, + const Expr *EndExpr, + bool *ContainerNeedsDereference) { + // Now that we know the loop variable and test expression, make sure they are + // valid. + bool BeginIsArrow = false; + bool EndIsArrow = false; + const Expr *BeginContainerExpr = + getContainerFromBeginEndCall(BeginExpr, /*IsBegin=*/true, &BeginIsArrow); + if (!BeginContainerExpr) + return nullptr; + + const Expr *EndContainerExpr = + getContainerFromBeginEndCall(EndExpr, /*IsBegin=*/false, &EndIsArrow); + // Disallow loops that try evil things like this (note the dot and arrow): + // for (IteratorType It = Obj.begin(), E = Obj->end(); It != E; ++It) { } + if (!EndContainerExpr || BeginIsArrow != EndIsArrow || + !areSameExpr(Context, EndContainerExpr, BeginContainerExpr)) + return nullptr; + + *ContainerNeedsDereference = BeginIsArrow; + return BeginContainerExpr; +} + +/// \brief Obtain the original source code text from a SourceRange. +static StringRef getStringFromRange(SourceManager &SourceMgr, + const LangOptions &LangOpts, + SourceRange Range) { + if (SourceMgr.getFileID(Range.getBegin()) != + SourceMgr.getFileID(Range.getEnd())) { + return StringRef(); // Empty string. + } + + return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr, + LangOpts); +} + +/// \brief If the given expression is actually a DeclRefExpr or a MemberExpr, +/// find and return the underlying ValueDecl; otherwise, return NULL. +static const ValueDecl *getReferencedVariable(const Expr *E) { + if (const DeclRefExpr *DRE = getDeclRef(E)) + return dyn_cast(DRE->getDecl()); + if (const auto *Mem = dyn_cast(E->IgnoreParenImpCasts())) + return dyn_cast(Mem->getMemberDecl()); + return nullptr; +} + +/// \brief Returns true when the given expression is a member expression +/// whose base is `this` (implicitly or not). +static bool isDirectMemberExpr(const Expr *E) { + if (const auto *Member = dyn_cast(E->IgnoreParenImpCasts())) + return isa(Member->getBase()->IgnoreParenImpCasts()); + return false; +} + +/// \brief Given an expression that represents an usage of an element from the +/// containter that we are iterating over, returns false when it can be +/// guaranteed this element cannot be modified as a result of this usage. +static bool canBeModified(ASTContext *Context, const Expr *E) { + if (E->getType().isConstQualified()) + return false; + auto Parents = Context->getParents(*E); + if (Parents.size() != 1) + return true; + if (const auto *Cast = Parents[0].get()) { + if ((Cast->getCastKind() == CK_NoOp && + Cast->getType() == E->getType().withConst()) || + (Cast->getCastKind() == CK_LValueToRValue && + !Cast->getType().isNull() && Cast->getType()->isFundamentalType())) + return false; + } + // FIXME: Make this function more generic. + return true; +} + +/// \brief Returns true when it can be guaranteed that the elements of the +/// container are not being modified. +static bool usagesAreConst(ASTContext *Context, const UsageResult &Usages) { + for (const Usage &U : Usages) { + // Lambda captures are just redeclarations (VarDecl) of the same variable, + // not expressions. If we want to know if a variable that is captured by + // reference can be modified in an usage inside the lambda's body, we need + // to find the expression corresponding to that particular usage, later in + // this loop. + if (U.Kind != Usage::UK_CaptureByCopy && U.Kind != Usage::UK_CaptureByRef && + canBeModified(Context, U.Expression)) + return false; + } + return true; +} + +/// \brief Returns true if the elements of the container are never accessed +/// by reference. +static bool usagesReturnRValues(const UsageResult &Usages) { + for (const auto &U : Usages) { + if (U.Expression && !U.Expression->isRValue()) + return false; + } + return true; +} + +/// \brief Returns true if the container is const-qualified. +static bool containerIsConst(const Expr *ContainerExpr, bool Dereference) { + if (const auto *VDec = getReferencedVariable(ContainerExpr)) { + QualType CType = VDec->getType(); + if (Dereference) { + if (!CType->isPointerType()) + return false; + CType = CType->getPointeeType(); + } + // If VDec is a reference to a container, Dereference is false, + // but we still need to check the const-ness of the underlying container + // type. + CType = CType.getNonReferenceType(); + return CType.isConstQualified(); + } + return false; +} + +LoopConvertCheck::RangeDescriptor::RangeDescriptor() + : ContainerNeedsDereference(false), DerefByConstRef(false), + DerefByValue(false) {} + +LoopConvertCheck::LoopConvertCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), TUInfo(new TUTrackingInfo), + MaxCopySize(std::stoull(Options.get("MaxCopySize", "16"))), + MinConfidence(StringSwitch( + Options.get("MinConfidence", "reasonable")) + .Case("safe", Confidence::CL_Safe) + .Case("risky", Confidence::CL_Risky) + .Default(Confidence::CL_Reasonable)), + NamingStyle(StringSwitch( + Options.get("NamingStyle", "CamelCase")) + .Case("camelBack", VariableNamer::NS_CamelBack) + .Case("lower_case", VariableNamer::NS_LowerCase) + .Case("UPPER_CASE", VariableNamer::NS_UpperCase) + .Default(VariableNamer::NS_CamelCase)) {} + +void LoopConvertCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MaxCopySize", std::to_string(MaxCopySize)); + SmallVector Confs{"risky", "reasonable", "safe"}; + Options.store(Opts, "MinConfidence", Confs[static_cast(MinConfidence)]); + + SmallVector Styles{"camelBack", "CamelCase", "lower_case", + "UPPER_CASE"}; + Options.store(Opts, "NamingStyle", Styles[static_cast(NamingStyle)]); +} + +void LoopConvertCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++. Because this checker is used for + // modernization, it is reasonable to run it on any C++ standard with the + // assumption the user is trying to modernize their codebase. + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(makeArrayLoopMatcher(), this); + Finder->addMatcher(makeIteratorLoopMatcher(), this); + Finder->addMatcher(makePseudoArrayLoopMatcher(), this); +} + +/// \brief Given the range of a single declaration, such as: +/// \code +/// unsigned &ThisIsADeclarationThatCanSpanSeveralLinesOfCode = +/// InitializationValues[I]; +/// next_instruction; +/// \endcode +/// Finds the range that has to be erased to remove this declaration without +/// leaving empty lines, by extending the range until the beginning of the +/// next instruction. +/// +/// We need to delete a potential newline after the deleted alias, as +/// clang-format will leave empty lines untouched. For all other formatting we +/// rely on clang-format to fix it. +void LoopConvertCheck::getAliasRange(SourceManager &SM, SourceRange &Range) { + bool Invalid = false; + const char *TextAfter = + SM.getCharacterData(Range.getEnd().getLocWithOffset(1), &Invalid); + if (Invalid) + return; + unsigned Offset = std::strspn(TextAfter, " \t\r\n"); + Range = + SourceRange(Range.getBegin(), Range.getEnd().getLocWithOffset(Offset)); +} + +/// \brief Computes the changes needed to convert a given for loop, and +/// applies them. +void LoopConvertCheck::doConversion( + ASTContext *Context, const VarDecl *IndexVar, + const ValueDecl *MaybeContainer, const UsageResult &Usages, + const DeclStmt *AliasDecl, bool AliasUseRequired, bool AliasFromForInit, + const ForStmt *Loop, RangeDescriptor Descriptor) { + auto Diag = diag(Loop->getForLoc(), "use range-based for loop instead"); + + std::string VarName; + bool VarNameFromAlias = (Usages.size() == 1) && AliasDecl; + bool AliasVarIsRef = false; + bool CanCopy = true; + + if (VarNameFromAlias) { + const auto *AliasVar = cast(AliasDecl->getSingleDecl()); + VarName = AliasVar->getName().str(); + + // Use the type of the alias if it's not the same + QualType AliasVarType = AliasVar->getType(); + assert(!AliasVarType.isNull() && "Type in VarDecl is null"); + if (AliasVarType->isReferenceType()) { + AliasVarType = AliasVarType.getNonReferenceType(); + AliasVarIsRef = true; + } + if (Descriptor.ElemType.isNull() || + !Context->hasSameUnqualifiedType(AliasVarType, Descriptor.ElemType)) + Descriptor.ElemType = AliasVarType; + + // We keep along the entire DeclStmt to keep the correct range here. + SourceRange ReplaceRange = AliasDecl->getSourceRange(); + + std::string ReplacementText; + if (AliasUseRequired) { + ReplacementText = VarName; + } else if (AliasFromForInit) { + // FIXME: Clang includes the location of the ';' but only for DeclStmt's + // in a for loop's init clause. Need to put this ';' back while removing + // the declaration of the alias variable. This is probably a bug. + ReplacementText = ";"; + } else { + // Avoid leaving empty lines or trailing whitespaces. + getAliasRange(Context->getSourceManager(), ReplaceRange); + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ReplaceRange), ReplacementText); + // No further replacements are made to the loop, since the iterator or index + // was used exactly once - in the initialization of AliasVar. + } else { + VariableNamer Namer(&TUInfo->getGeneratedDecls(), + &TUInfo->getParentFinder().getStmtToParentStmtMap(), + Loop, IndexVar, MaybeContainer, Context, NamingStyle); + VarName = Namer.createIndexName(); + // First, replace all usages of the array subscript expression with our new + // variable. + for (const auto &Usage : Usages) { + std::string ReplaceText; + SourceRange Range = Usage.Range; + if (Usage.Expression) { + // If this is an access to a member through the arrow operator, after + // the replacement it must be accessed through the '.' operator. + ReplaceText = Usage.Kind == Usage::UK_MemberThroughArrow ? VarName + "." + : VarName; + auto Parents = Context->getParents(*Usage.Expression); + if (Parents.size() == 1) { + if (const auto *Paren = Parents[0].get()) { + // Usage.Expression will be replaced with the new index variable, + // and parenthesis around a simple DeclRefExpr can always be + // removed. + Range = Paren->getSourceRange(); + } else if (const auto *UOP = Parents[0].get()) { + // If we are taking the address of the loop variable, then we must + // not use a copy, as it would mean taking the address of the loop's + // local index instead. + // FIXME: This won't catch cases where the address is taken outside + // of the loop's body (for instance, in a function that got the + // loop's index as a const reference parameter), or where we take + // the address of a member (like "&Arr[i].A.B.C"). + if (UOP->getOpcode() == UO_AddrOf) + CanCopy = false; + } + } + } else { + // The Usage expression is only null in case of lambda captures (which + // are VarDecl). If the index is captured by value, add '&' to capture + // by reference instead. + ReplaceText = + Usage.Kind == Usage::UK_CaptureByCopy ? "&" + VarName : VarName; + } + TUInfo->getReplacedVars().insert(std::make_pair(Loop, IndexVar)); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(Range), ReplaceText); + } + } + + // Now, we need to construct the new range expression. + SourceRange ParenRange(Loop->getLParenLoc(), Loop->getRParenLoc()); + + QualType Type = Context->getAutoDeductType(); + if (!Descriptor.ElemType.isNull() && Descriptor.ElemType->isFundamentalType()) + Type = Descriptor.ElemType.getUnqualifiedType(); + + // If the new variable name is from the aliased variable, then the reference + // type for the new variable should only be used if the aliased variable was + // declared as a reference. + bool IsCheapToCopy = + !Descriptor.ElemType.isNull() && + Descriptor.ElemType.isTriviallyCopyableType(*Context) && + // TypeInfo::Width is in bits. + Context->getTypeInfo(Descriptor.ElemType).Width <= 8 * MaxCopySize; + bool UseCopy = CanCopy && ((VarNameFromAlias && !AliasVarIsRef) || + (Descriptor.DerefByConstRef && IsCheapToCopy)); + + if (!UseCopy) { + if (Descriptor.DerefByConstRef) { + Type = Context->getLValueReferenceType(Context->getConstType(Type)); + } else if (Descriptor.DerefByValue) { + if (!IsCheapToCopy) + Type = Context->getRValueReferenceType(Type); + } else { + Type = Context->getLValueReferenceType(Type); + } + } + + StringRef MaybeDereference = Descriptor.ContainerNeedsDereference ? "*" : ""; + std::string TypeString = Type.getAsString(getLangOpts()); + std::string Range = ("(" + TypeString + " " + VarName + " : " + + MaybeDereference + Descriptor.ContainerString + ")") + .str(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParenRange), Range); + TUInfo->getGeneratedDecls().insert(make_pair(Loop, VarName)); +} + +/// \brief Returns a string which refers to the container iterated over. +StringRef LoopConvertCheck::getContainerString(ASTContext *Context, + const ForStmt *Loop, + const Expr *ContainerExpr) { + StringRef ContainerString; + if (isa(ContainerExpr->IgnoreParenImpCasts())) { + ContainerString = "this"; + } else { + ContainerString = + getStringFromRange(Context->getSourceManager(), Context->getLangOpts(), + ContainerExpr->getSourceRange()); + } + + return ContainerString; +} + +/// \brief Determines what kind of 'auto' must be used after converting a for +/// loop that iterates over an array or pseudoarray. +void LoopConvertCheck::getArrayLoopQualifiers(ASTContext *Context, + const BoundNodes &Nodes, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor) { + // On arrays and pseudoarrays, we must figure out the qualifiers from the + // usages. + if (usagesAreConst(Context, Usages) || + containerIsConst(ContainerExpr, Descriptor.ContainerNeedsDereference)) { + Descriptor.DerefByConstRef = true; + } + if (usagesReturnRValues(Usages)) { + // If the index usages (dereference, subscript, at, ...) return rvalues, + // then we should not use a reference, because we need to keep the code + // correct if it mutates the returned objects. + Descriptor.DerefByValue = true; + } + // Try to find the type of the elements on the container, to check if + // they are trivially copyable. + for (const Usage &U : Usages) { + if (!U.Expression || U.Expression->getType().isNull()) + continue; + QualType Type = U.Expression->getType().getCanonicalType(); + if (U.Kind == Usage::UK_MemberThroughArrow) { + if (!Type->isPointerType()) { + continue; + } + Type = Type->getPointeeType(); + } + Descriptor.ElemType = Type; + } +} + +/// \brief Determines what kind of 'auto' must be used after converting an +/// iterator based for loop. +void LoopConvertCheck::getIteratorLoopQualifiers(ASTContext *Context, + const BoundNodes &Nodes, + RangeDescriptor &Descriptor) { + // The matchers for iterator loops provide bound nodes to obtain this + // information. + const auto *InitVar = Nodes.getNodeAs(InitVarName); + QualType CanonicalInitVarType = InitVar->getType().getCanonicalType(); + const auto *DerefByValueType = + Nodes.getNodeAs(DerefByValueResultName); + Descriptor.DerefByValue = DerefByValueType; + + if (Descriptor.DerefByValue) { + // If the dereference operator returns by value then test for the + // canonical const qualification of the init variable type. + Descriptor.DerefByConstRef = CanonicalInitVarType.isConstQualified(); + Descriptor.ElemType = *DerefByValueType; + } else { + if (const auto *DerefType = + Nodes.getNodeAs(DerefByRefResultName)) { + // A node will only be bound with DerefByRefResultName if we're dealing + // with a user-defined iterator type. Test the const qualification of + // the reference type. + auto ValueType = DerefType->getNonReferenceType(); + + Descriptor.DerefByConstRef = ValueType.isConstQualified(); + Descriptor.ElemType = ValueType; + } else { + // By nature of the matcher this case is triggered only for built-in + // iterator types (i.e. pointers). + assert(isa(CanonicalInitVarType) && + "Non-class iterator type is not a pointer type"); + + // We test for const qualification of the pointed-at type. + Descriptor.DerefByConstRef = + CanonicalInitVarType->getPointeeType().isConstQualified(); + Descriptor.ElemType = CanonicalInitVarType->getPointeeType(); + } + } +} + +/// \brief Determines the parameters needed to build the range replacement. +void LoopConvertCheck::determineRangeDescriptor( + ASTContext *Context, const BoundNodes &Nodes, const ForStmt *Loop, + LoopFixerKind FixerKind, const Expr *ContainerExpr, + const UsageResult &Usages, RangeDescriptor &Descriptor) { + Descriptor.ContainerString = getContainerString(Context, Loop, ContainerExpr); + + if (FixerKind == LFK_Iterator) + getIteratorLoopQualifiers(Context, Nodes, Descriptor); + else + getArrayLoopQualifiers(Context, Nodes, ContainerExpr, Usages, Descriptor); +} + +/// \brief Check some of the conditions that must be met for the loop to be +/// convertible. +bool LoopConvertCheck::isConvertible(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, + LoopFixerKind FixerKind) { + // If we already modified the range of this for loop, don't do any further + // updates on this iteration. + if (TUInfo->getReplacedVars().count(Loop)) + return false; + + // Check that we have exactly one index variable and at most one end variable. + const auto *LoopVar = Nodes.getNodeAs(IncrementVarName); + const auto *CondVar = Nodes.getNodeAs(ConditionVarName); + const auto *InitVar = Nodes.getNodeAs(InitVarName); + if (!areSameVariable(LoopVar, CondVar) || !areSameVariable(LoopVar, InitVar)) + return false; + const auto *EndVar = Nodes.getNodeAs(EndVarName); + const auto *ConditionEndVar = Nodes.getNodeAs(ConditionEndVarName); + if (EndVar && !areSameVariable(EndVar, ConditionEndVar)) + return false; + + // FIXME: Try to put most of this logic inside a matcher. + if (FixerKind == LFK_Iterator) { + QualType InitVarType = InitVar->getType(); + QualType CanonicalInitVarType = InitVarType.getCanonicalType(); + + const auto *BeginCall = Nodes.getNodeAs(BeginCallName); + assert(BeginCall && "Bad Callback. No begin call expression"); + QualType CanonicalBeginType = + BeginCall->getMethodDecl()->getReturnType().getCanonicalType(); + if (CanonicalBeginType->isPointerType() && + CanonicalInitVarType->isPointerType()) { + // If the initializer and the variable are both pointers check if the + // un-qualified pointee types match, otherwise we don't use auto. + if (!Context->hasSameUnqualifiedType( + CanonicalBeginType->getPointeeType(), + CanonicalInitVarType->getPointeeType())) + return false; + } else if (!Context->hasSameType(CanonicalInitVarType, + CanonicalBeginType)) { + // Check for qualified types to avoid conversions from non-const to const + // iterator types. + return false; + } + } else if (FixerKind == LFK_PseudoArray) { + // This call is required to obtain the container. + const auto *EndCall = Nodes.getNodeAs(EndCallName); + if (!EndCall || !dyn_cast(EndCall->getCallee())) + return false; + } + return true; +} + +void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) { + const BoundNodes &Nodes = Result.Nodes; + Confidence ConfidenceLevel(Confidence::CL_Safe); + ASTContext *Context = Result.Context; + + const ForStmt *Loop; + LoopFixerKind FixerKind; + RangeDescriptor Descriptor; + + if ((Loop = Nodes.getNodeAs(LoopNameArray))) { + FixerKind = LFK_Array; + } else if ((Loop = Nodes.getNodeAs(LoopNameIterator))) { + FixerKind = LFK_Iterator; + } else { + Loop = Nodes.getNodeAs(LoopNamePseudoArray); + assert(Loop && "Bad Callback. No for statement"); + FixerKind = LFK_PseudoArray; + } + + if (!isConvertible(Context, Nodes, Loop, FixerKind)) + return; + + const auto *LoopVar = Nodes.getNodeAs(IncrementVarName); + const auto *EndVar = Nodes.getNodeAs(EndVarName); + + // If the loop calls end()/size() after each iteration, lower our confidence + // level. + if (FixerKind != LFK_Array && !EndVar) + ConfidenceLevel.lowerTo(Confidence::CL_Reasonable); + + // If the end comparison isn't a variable, we can try to work with the + // expression the loop variable is being tested against instead. + const auto *EndCall = Nodes.getNodeAs(EndCallName); + const auto *BoundExpr = Nodes.getNodeAs(ConditionBoundName); + + // Find container expression of iterators and pseudoarrays, and determine if + // this expression needs to be dereferenced to obtain the container. + // With array loops, the container is often discovered during the + // ForLoopIndexUseVisitor traversal. + const Expr *ContainerExpr = nullptr; + if (FixerKind == LFK_Iterator) { + ContainerExpr = findContainer(Context, LoopVar->getInit(), + EndVar ? EndVar->getInit() : EndCall, + &Descriptor.ContainerNeedsDereference); + } else if (FixerKind == LFK_PseudoArray) { + ContainerExpr = EndCall->getImplicitObjectArgument(); + Descriptor.ContainerNeedsDereference = + dyn_cast(EndCall->getCallee())->isArrow(); + } + + // We must know the container or an array length bound. + if (!ContainerExpr && !BoundExpr) + return; + + ForLoopIndexUseVisitor Finder(Context, LoopVar, EndVar, ContainerExpr, + BoundExpr, + Descriptor.ContainerNeedsDereference); + + // Find expressions and variables on which the container depends. + if (ContainerExpr) { + ComponentFinderASTVisitor ComponentFinder; + ComponentFinder.findExprComponents(ContainerExpr->IgnoreParenImpCasts()); + Finder.addComponents(ComponentFinder.getComponents()); + } + + // Find usages of the loop index. If they are not used in a convertible way, + // stop here. + if (!Finder.findAndVerifyUsages(Loop->getBody())) + return; + ConfidenceLevel.lowerTo(Finder.getConfidenceLevel()); + + // Obtain the container expression, if we don't have it yet. + if (FixerKind == LFK_Array) { + ContainerExpr = Finder.getContainerIndexed()->IgnoreParenImpCasts(); + + // Very few loops are over expressions that generate arrays rather than + // array variables. Consider loops over arrays that aren't just represented + // by a variable to be risky conversions. + if (!getReferencedVariable(ContainerExpr) && + !isDirectMemberExpr(ContainerExpr)) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + } + + // Find out which qualifiers we have to use in the loop range. + const UsageResult &Usages = Finder.getUsages(); + determineRangeDescriptor(Context, Nodes, Loop, FixerKind, ContainerExpr, + Usages, Descriptor); + + // Ensure that we do not try to move an expression dependent on a local + // variable declared inside the loop outside of it. + // FIXME: Determine when the external dependency isn't an expression converted + // by another loop. + TUInfo->getParentFinder().gatherAncestors(Context->getTranslationUnitDecl()); + DependencyFinderASTVisitor DependencyFinder( + &TUInfo->getParentFinder().getStmtToParentStmtMap(), + &TUInfo->getParentFinder().getDeclToParentStmtMap(), + &TUInfo->getReplacedVars(), Loop); + + if (DependencyFinder.dependsOnInsideVariable(ContainerExpr) || + Descriptor.ContainerString.empty() || Usages.empty() || + ConfidenceLevel.getLevel() < MinConfidence) + return; + + doConversion(Context, LoopVar, getReferencedVariable(ContainerExpr), Usages, + Finder.getAliasDecl(), Finder.aliasUseRequired(), + Finder.aliasFromForInit(), Loop, Descriptor); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/LoopConvertCheck.h b/clang-tidy/modernize/LoopConvertCheck.h new file mode 100644 index 000000000..75ab25aa1 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertCheck.h @@ -0,0 +1,78 @@ +//===--- LoopConvertCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H + +#include "../ClangTidy.h" +#include "LoopConvertUtils.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class LoopConvertCheck : public ClangTidyCheck { +public: + LoopConvertCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + struct RangeDescriptor { + RangeDescriptor(); + bool ContainerNeedsDereference; + bool DerefByConstRef; + bool DerefByValue; + std::string ContainerString; + QualType ElemType; + }; + + void getAliasRange(SourceManager &SM, SourceRange &DeclRange); + + void doConversion(ASTContext *Context, const VarDecl *IndexVar, + const ValueDecl *MaybeContainer, const UsageResult &Usages, + const DeclStmt *AliasDecl, bool AliasUseRequired, + bool AliasFromForInit, const ForStmt *Loop, + RangeDescriptor Descriptor); + + StringRef getContainerString(ASTContext *Context, const ForStmt *Loop, + const Expr *ContainerExpr); + + void getArrayLoopQualifiers(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor); + + void getIteratorLoopQualifiers(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + RangeDescriptor &Descriptor); + + void determineRangeDescriptor(ASTContext *Context, + const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, LoopFixerKind FixerKind, + const Expr *ContainerExpr, + const UsageResult &Usages, + RangeDescriptor &Descriptor); + + bool isConvertible(ASTContext *Context, const ast_matchers::BoundNodes &Nodes, + const ForStmt *Loop, LoopFixerKind FixerKind); + + std::unique_ptr TUInfo; + const unsigned long long MaxCopySize; + const Confidence::Level MinConfidence; + const VariableNamer::NamingStyle NamingStyle; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H diff --git a/clang-tidy/modernize/LoopConvertUtils.cpp b/clang-tidy/modernize/LoopConvertUtils.cpp new file mode 100644 index 000000000..f65f7a166 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertUtils.cpp @@ -0,0 +1,909 @@ +//===--- LoopConvertUtils.cpp - clang-tidy --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LoopConvertUtils.h" +#include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/Lambda.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include +#include +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Tracks a stack of parent statements during traversal. +/// +/// All this really does is inject push_back() before running +/// RecursiveASTVisitor::TraverseStmt() and pop_back() afterwards. The Stmt atop +/// the stack is the parent of the current statement (NULL for the topmost +/// statement). +bool StmtAncestorASTVisitor::TraverseStmt(Stmt *Statement) { + StmtAncestors.insert(std::make_pair(Statement, StmtStack.back())); + StmtStack.push_back(Statement); + RecursiveASTVisitor::TraverseStmt(Statement); + StmtStack.pop_back(); + return true; +} + +/// \brief Keep track of the DeclStmt associated with each VarDecl. +/// +/// Combined with StmtAncestors, this provides roughly the same information as +/// Scope, as we can map a VarDecl to its DeclStmt, then walk up the parent tree +/// using StmtAncestors. +bool StmtAncestorASTVisitor::VisitDeclStmt(DeclStmt *Decls) { + for (const auto *decl : Decls->decls()) { + if (const auto *V = dyn_cast(decl)) + DeclParents.insert(std::make_pair(V, Decls)); + } + return true; +} + +/// \brief record the DeclRefExpr as part of the parent expression. +bool ComponentFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *E) { + Components.push_back(E); + return true; +} + +/// \brief record the MemberExpr as part of the parent expression. +bool ComponentFinderASTVisitor::VisitMemberExpr(MemberExpr *Member) { + Components.push_back(Member); + return true; +} + +/// \brief Forward any DeclRefExprs to a check on the referenced variable +/// declaration. +bool DependencyFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (auto *V = dyn_cast_or_null(DeclRef->getDecl())) + return VisitVarDecl(V); + return true; +} + +/// \brief Determine if any this variable is declared inside the ContainingStmt. +bool DependencyFinderASTVisitor::VisitVarDecl(VarDecl *V) { + const Stmt *Curr = DeclParents->lookup(V); + // First, see if the variable was declared within an inner scope of the loop. + while (Curr != nullptr) { + if (Curr == ContainingStmt) { + DependsOnInsideVariable = true; + return false; + } + Curr = StmtParents->lookup(Curr); + } + + // Next, check if the variable was removed from existence by an earlier + // iteration. + for (const auto &I : *ReplacedVars) { + if (I.second == V) { + DependsOnInsideVariable = true; + return false; + } + } + return true; +} + +/// \brief If we already created a variable for TheLoop, check to make sure +/// that the name was not already taken. +bool DeclFinderASTVisitor::VisitForStmt(ForStmt *TheLoop) { + StmtGeneratedVarNameMap::const_iterator I = GeneratedDecls->find(TheLoop); + if (I != GeneratedDecls->end() && I->second == Name) { + Found = true; + return false; + } + return true; +} + +/// \brief If any named declaration within the AST subtree has the same name, +/// then consider Name already taken. +bool DeclFinderASTVisitor::VisitNamedDecl(NamedDecl *D) { + const IdentifierInfo *Ident = D->getIdentifier(); + if (Ident && Ident->getName() == Name) { + Found = true; + return false; + } + return true; +} + +/// \brief Forward any declaration references to the actual check on the +/// referenced declaration. +bool DeclFinderASTVisitor::VisitDeclRefExpr(DeclRefExpr *DeclRef) { + if (auto *D = dyn_cast(DeclRef->getDecl())) + return VisitNamedDecl(D); + return true; +} + +/// \brief If the new variable name conflicts with any type used in the loop, +/// then we mark that variable name as taken. +bool DeclFinderASTVisitor::VisitTypeLoc(TypeLoc TL) { + QualType QType = TL.getType(); + + // Check if our name conflicts with a type, to handle for typedefs. + if (QType.getAsString() == Name) { + Found = true; + return false; + } + // Check for base type conflicts. For example, when a struct is being + // referenced in the body of the loop, the above getAsString() will return the + // whole type (ex. "struct s"), but will be caught here. + if (const IdentifierInfo *Ident = QType.getBaseTypeIdentifier()) { + if (Ident->getName() == Name) { + Found = true; + return false; + } + } + return true; +} + +/// \brief Look through conversion/copy constructors to find the explicit +/// initialization expression, returning it is found. +/// +/// The main idea is that given +/// vector v; +/// we consider either of these initializations +/// vector::iterator it = v.begin(); +/// vector::iterator it(v.begin()); +/// and retrieve `v.begin()` as the expression used to initialize `it` but do +/// not include +/// vector::iterator it; +/// vector::iterator it(v.begin(), 0); // if this constructor existed +/// as being initialized from `v.begin()` +const Expr *digThroughConstructors(const Expr *E) { + if (!E) + return nullptr; + E = E->IgnoreImplicit(); + if (const auto *ConstructExpr = dyn_cast(E)) { + // The initial constructor must take exactly one parameter, but base class + // and deferred constructors can take more. + if (ConstructExpr->getNumArgs() != 1 || + ConstructExpr->getConstructionKind() != CXXConstructExpr::CK_Complete) + return nullptr; + E = ConstructExpr->getArg(0); + if (const auto *Temp = dyn_cast(E)) + E = Temp->GetTemporaryExpr(); + return digThroughConstructors(E); + } + return E; +} + +/// \brief Returns true when two Exprs are equivalent. +bool areSameExpr(ASTContext *Context, const Expr *First, const Expr *Second) { + if (!First || !Second) + return false; + + llvm::FoldingSetNodeID FirstID, SecondID; + First->Profile(FirstID, *Context, true); + Second->Profile(SecondID, *Context, true); + return FirstID == SecondID; +} + +/// \brief Returns the DeclRefExpr represented by E, or NULL if there isn't one. +const DeclRefExpr *getDeclRef(const Expr *E) { + return dyn_cast(E->IgnoreParenImpCasts()); +} + +/// \brief Returns true when two ValueDecls are the same variable. +bool areSameVariable(const ValueDecl *First, const ValueDecl *Second) { + return First && Second && + First->getCanonicalDecl() == Second->getCanonicalDecl(); +} + +/// \brief Determines if an expression is a declaration reference to a +/// particular variable. +static bool exprReferencesVariable(const ValueDecl *Target, const Expr *E) { + if (!Target || !E) + return false; + const DeclRefExpr *Decl = getDeclRef(E); + return Decl && areSameVariable(Target, Decl->getDecl()); +} + +/// \brief If the expression is a dereference or call to operator*(), return the +/// operand. Otherwise, return NULL. +static const Expr *getDereferenceOperand(const Expr *E) { + if (const auto *Uop = dyn_cast(E)) + return Uop->getOpcode() == UO_Deref ? Uop->getSubExpr() : nullptr; + + if (const auto *OpCall = dyn_cast(E)) { + return OpCall->getOperator() == OO_Star && OpCall->getNumArgs() == 1 + ? OpCall->getArg(0) + : nullptr; + } + + return nullptr; +} + +/// \brief Returns true when the Container contains an Expr equivalent to E. +template +static bool containsExpr(ASTContext *Context, const ContainerT *Container, + const Expr *E) { + llvm::FoldingSetNodeID ID; + E->Profile(ID, *Context, true); + for (const auto &I : *Container) { + if (ID == I.second) + return true; + } + return false; +} + +/// \brief Returns true when the index expression is a declaration reference to +/// IndexVar. +/// +/// If the index variable is `index`, this function returns true on +/// arrayExpression[index]; +/// containerExpression[index]; +/// but not +/// containerExpression[notIndex]; +static bool isIndexInSubscriptExpr(const Expr *IndexExpr, + const VarDecl *IndexVar) { + const DeclRefExpr *Idx = getDeclRef(IndexExpr); + return Idx && Idx->getType()->isIntegerType() && + areSameVariable(IndexVar, Idx->getDecl()); +} + +/// \brief Returns true when the index expression is a declaration reference to +/// IndexVar, Obj is the same expression as SourceExpr after all parens and +/// implicit casts are stripped off. +/// +/// If PermitDeref is true, IndexExpression may +/// be a dereference (overloaded or builtin operator*). +/// +/// This function is intended for array-like containers, as it makes sure that +/// both the container and the index match. +/// If the loop has index variable `index` and iterates over `container`, then +/// isIndexInSubscriptExpr returns true for +/// \code +/// container[index] +/// container.at(index) +/// container->at(index) +/// \endcode +/// but not for +/// \code +/// container[notIndex] +/// notContainer[index] +/// \endcode +/// If PermitDeref is true, then isIndexInSubscriptExpr additionally returns +/// true on these expressions: +/// \code +/// (*container)[index] +/// (*container).at(index) +/// \endcode +static bool isIndexInSubscriptExpr(ASTContext *Context, const Expr *IndexExpr, + const VarDecl *IndexVar, const Expr *Obj, + const Expr *SourceExpr, bool PermitDeref) { + if (!SourceExpr || !Obj || !isIndexInSubscriptExpr(IndexExpr, IndexVar)) + return false; + + if (areSameExpr(Context, SourceExpr->IgnoreParenImpCasts(), + Obj->IgnoreParenImpCasts())) + return true; + + if (const Expr *InnerObj = getDereferenceOperand(Obj->IgnoreParenImpCasts())) + if (PermitDeref && areSameExpr(Context, SourceExpr->IgnoreParenImpCasts(), + InnerObj->IgnoreParenImpCasts())) + return true; + + return false; +} + +/// \brief Returns true when Opcall is a call a one-parameter dereference of +/// IndexVar. +/// +/// For example, if the index variable is `index`, returns true for +/// *index +/// but not +/// index +/// *notIndex +static bool isDereferenceOfOpCall(const CXXOperatorCallExpr *OpCall, + const VarDecl *IndexVar) { + return OpCall->getOperator() == OO_Star && OpCall->getNumArgs() == 1 && + exprReferencesVariable(IndexVar, OpCall->getArg(0)); +} + +/// \brief Returns true when Uop is a dereference of IndexVar. +/// +/// For example, if the index variable is `index`, returns true for +/// *index +/// but not +/// index +/// *notIndex +static bool isDereferenceOfUop(const UnaryOperator *Uop, + const VarDecl *IndexVar) { + return Uop->getOpcode() == UO_Deref && + exprReferencesVariable(IndexVar, Uop->getSubExpr()); +} + +/// \brief Determines whether the given Decl defines a variable initialized to +/// the loop object. +/// +/// This is intended to find cases such as +/// \code +/// for (int i = 0; i < arraySize(arr); ++i) { +/// T t = arr[i]; +/// // use t, do not use i +/// } +/// \endcode +/// and +/// \code +/// for (iterator i = container.begin(), e = container.end(); i != e; ++i) { +/// T t = *i; +/// // use t, do not use i +/// } +/// \endcode +static bool isAliasDecl(ASTContext *Context, const Decl *TheDecl, + const VarDecl *IndexVar) { + const auto *VDecl = dyn_cast(TheDecl); + if (!VDecl) + return false; + if (!VDecl->hasInit()) + return false; + + bool OnlyCasts = true; + const Expr *Init = VDecl->getInit()->IgnoreParenImpCasts(); + if (Init && isa(Init)) { + Init = digThroughConstructors(Init); + OnlyCasts = false; + } + if (!Init) + return false; + + // Check that the declared type is the same as (or a reference to) the + // container type. + if (!OnlyCasts) { + QualType InitType = Init->getType(); + QualType DeclarationType = VDecl->getType(); + if (!DeclarationType.isNull() && DeclarationType->isReferenceType()) + DeclarationType = DeclarationType.getNonReferenceType(); + + if (InitType.isNull() || DeclarationType.isNull() || + !Context->hasSameUnqualifiedType(DeclarationType, InitType)) + return false; + } + + switch (Init->getStmtClass()) { + case Stmt::ArraySubscriptExprClass: { + const auto *E = cast(Init); + // We don't really care which array is used here. We check to make sure + // it was the correct one later, since the AST will traverse it next. + return isIndexInSubscriptExpr(E->getIdx(), IndexVar); + } + + case Stmt::UnaryOperatorClass: + return isDereferenceOfUop(cast(Init), IndexVar); + + case Stmt::CXXOperatorCallExprClass: { + const auto *OpCall = cast(Init); + if (OpCall->getOperator() == OO_Star) + return isDereferenceOfOpCall(OpCall, IndexVar); + if (OpCall->getOperator() == OO_Subscript) { + assert(OpCall->getNumArgs() == 2); + return isIndexInSubscriptExpr(OpCall->getArg(1), IndexVar); + } + break; + } + + case Stmt::CXXMemberCallExprClass: { + const auto *MemCall = cast(Init); + // This check is needed because getMethodDecl can return nullptr if the + // callee is a member function pointer. + const auto *MDecl = MemCall->getMethodDecl(); + if (MDecl && !isa(MDecl) && + MDecl->getNameAsString() == "at" && MemCall->getNumArgs() == 1) { + return isIndexInSubscriptExpr(MemCall->getArg(0), IndexVar); + } + return false; + } + + default: + break; + } + return false; +} + +/// \brief Determines whether the bound of a for loop condition expression is +/// the same as the statically computable size of ArrayType. +/// +/// Given +/// \code +/// const int N = 5; +/// int arr[N]; +/// \endcode +/// This is intended to permit +/// \code +/// for (int i = 0; i < N; ++i) { /* use arr[i] */ } +/// for (int i = 0; i < arraysize(arr); ++i) { /* use arr[i] */ } +/// \endcode +static bool arrayMatchesBoundExpr(ASTContext *Context, + const QualType &ArrayType, + const Expr *ConditionExpr) { + if (!ConditionExpr || ConditionExpr->isValueDependent()) + return false; + const ConstantArrayType *ConstType = + Context->getAsConstantArrayType(ArrayType); + if (!ConstType) + return false; + llvm::APSInt ConditionSize; + if (!ConditionExpr->isIntegerConstantExpr(ConditionSize, *Context)) + return false; + llvm::APSInt ArraySize(ConstType->getSize()); + return llvm::APSInt::isSameValue(ConditionSize, ArraySize); +} + +ForLoopIndexUseVisitor::ForLoopIndexUseVisitor(ASTContext *Context, + const VarDecl *IndexVar, + const VarDecl *EndVar, + const Expr *ContainerExpr, + const Expr *ArrayBoundExpr, + bool ContainerNeedsDereference) + : Context(Context), IndexVar(IndexVar), EndVar(EndVar), + ContainerExpr(ContainerExpr), ArrayBoundExpr(ArrayBoundExpr), + ContainerNeedsDereference(ContainerNeedsDereference), + OnlyUsedAsIndex(true), AliasDecl(nullptr), + ConfidenceLevel(Confidence::CL_Safe), NextStmtParent(nullptr), + CurrStmtParent(nullptr), ReplaceWithAliasUse(false), + AliasFromForInit(false) { + if (ContainerExpr) + addComponent(ContainerExpr); +} + +bool ForLoopIndexUseVisitor::findAndVerifyUsages(const Stmt *Body) { + TraverseStmt(const_cast(Body)); + return OnlyUsedAsIndex && ContainerExpr; +} + +void ForLoopIndexUseVisitor::addComponents(const ComponentVector &Components) { + // FIXME: add sort(on ID)+unique to avoid extra work. + for (const auto &I : Components) + addComponent(I); +} + +void ForLoopIndexUseVisitor::addComponent(const Expr *E) { + llvm::FoldingSetNodeID ID; + const Expr *Node = E->IgnoreParenImpCasts(); + Node->Profile(ID, *Context, true); + DependentExprs.push_back(std::make_pair(Node, ID)); +} + +void ForLoopIndexUseVisitor::addUsage(const Usage &U) { + SourceLocation Begin = U.Range.getBegin(); + if (Begin.isMacroID()) + Begin = Context->getSourceManager().getSpellingLoc(Begin); + + if (UsageLocations.insert(Begin).second) + Usages.push_back(U); +} + +/// \brief If the unary operator is a dereference of IndexVar, include it +/// as a valid usage and prune the traversal. +/// +/// For example, if container.begin() and container.end() both return pointers +/// to int, this makes sure that the initialization for `k` is not counted as an +/// unconvertible use of the iterator `i`. +/// \code +/// for (int *i = container.begin(), *e = container.end(); i != e; ++i) { +/// int k = *i + 2; +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseUnaryDeref(UnaryOperator *Uop) { + // If we dereference an iterator that's actually a pointer, count the + // occurrence. + if (isDereferenceOfUop(Uop, IndexVar)) { + addUsage(Usage(Uop)); + return true; + } + + return VisitorBase::TraverseUnaryOperator(Uop); +} + +/// \brief If the member expression is operator-> (overloaded or not) on +/// IndexVar, include it as a valid usage and prune the traversal. +/// +/// For example, given +/// \code +/// struct Foo { int bar(); int x; }; +/// vector v; +/// \endcode +/// the following uses will be considered convertible: +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int b = i->bar(); +/// int k = i->x + 1; +/// } +/// \endcode +/// though +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int k = i.insert(1); +/// } +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// int b = e->bar(); +/// } +/// \endcode +/// will not. +bool ForLoopIndexUseVisitor::TraverseMemberExpr(MemberExpr *Member) { + const Expr *Base = Member->getBase(); + const DeclRefExpr *Obj = getDeclRef(Base); + const Expr *ResultExpr = Member; + QualType ExprType; + if (const auto *Call = + dyn_cast(Base->IgnoreParenImpCasts())) { + // If operator->() is a MemberExpr containing a CXXOperatorCallExpr, then + // the MemberExpr does not have the expression we want. We therefore catch + // that instance here. + // For example, if vector::iterator defines operator->(), then the + // example `i->bar()` at the top of this function is a CXXMemberCallExpr + // referring to `i->` as the member function called. We want just `i`, so + // we take the argument to operator->() as the base object. + if (Call->getOperator() == OO_Arrow) { + assert(Call->getNumArgs() == 1 && + "Operator-> takes more than one argument"); + Obj = getDeclRef(Call->getArg(0)); + ResultExpr = Obj; + ExprType = Call->getCallReturnType(*Context); + } + } + + if (Obj && exprReferencesVariable(IndexVar, Obj)) { + // Member calls on the iterator with '.' are not allowed. + if (!Member->isArrow()) { + OnlyUsedAsIndex = false; + return true; + } + + if (ExprType.isNull()) + ExprType = Obj->getType(); + + if (!ExprType->isPointerType()) + return false; + + // FIXME: This works around not having the location of the arrow operator. + // Consider adding OperatorLoc to MemberExpr? + SourceLocation ArrowLoc = Lexer::getLocForEndOfToken( + Base->getExprLoc(), 0, Context->getSourceManager(), + Context->getLangOpts()); + // If something complicated is happening (i.e. the next token isn't an + // arrow), give up on making this work. + if (ArrowLoc.isValid()) { + addUsage(Usage(ResultExpr, Usage::UK_MemberThroughArrow, + SourceRange(Base->getExprLoc(), ArrowLoc))); + return true; + } + } + return VisitorBase::TraverseMemberExpr(Member); +} + +/// \brief If a member function call is the at() accessor on the container with +/// IndexVar as the single argument, include it as a valid usage and prune +/// the traversal. +/// +/// Member calls on other objects will not be permitted. +/// Calls on the iterator object are not permitted, unless done through +/// operator->(). The one exception is allowing vector::at() for pseudoarrays. +bool ForLoopIndexUseVisitor::TraverseCXXMemberCallExpr( + CXXMemberCallExpr *MemberCall) { + auto *Member = + dyn_cast(MemberCall->getCallee()->IgnoreParenImpCasts()); + if (!Member) + return VisitorBase::TraverseCXXMemberCallExpr(MemberCall); + + // We specifically allow an accessor named "at" to let STL in, though + // this is restricted to pseudo-arrays by requiring a single, integer + // argument. + const IdentifierInfo *Ident = Member->getMemberDecl()->getIdentifier(); + if (Ident && Ident->isStr("at") && MemberCall->getNumArgs() == 1) { + if (isIndexInSubscriptExpr(Context, MemberCall->getArg(0), IndexVar, + Member->getBase(), ContainerExpr, + ContainerNeedsDereference)) { + addUsage(Usage(MemberCall)); + return true; + } + } + + if (containsExpr(Context, &DependentExprs, Member->getBase())) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + + return VisitorBase::TraverseCXXMemberCallExpr(MemberCall); +} + +/// \brief If an overloaded operator call is a dereference of IndexVar or +/// a subscript of the container with IndexVar as the single argument, +/// include it as a valid usage and prune the traversal. +/// +/// For example, given +/// \code +/// struct Foo { int bar(); int x; }; +/// vector v; +/// void f(Foo); +/// \endcode +/// the following uses will be considered convertible: +/// \code +/// for (vector::iterator i = v.begin(), e = v.end(); i != e; ++i) { +/// f(*i); +/// } +/// for (int i = 0; i < v.size(); ++i) { +/// int i = v[i] + 1; +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseCXXOperatorCallExpr( + CXXOperatorCallExpr *OpCall) { + switch (OpCall->getOperator()) { + case OO_Star: + if (isDereferenceOfOpCall(OpCall, IndexVar)) { + addUsage(Usage(OpCall)); + return true; + } + break; + + case OO_Subscript: + if (OpCall->getNumArgs() != 2) + break; + if (isIndexInSubscriptExpr(Context, OpCall->getArg(1), IndexVar, + OpCall->getArg(0), ContainerExpr, + ContainerNeedsDereference)) { + addUsage(Usage(OpCall)); + return true; + } + break; + + default: + break; + } + return VisitorBase::TraverseCXXOperatorCallExpr(OpCall); +} + +/// \brief If we encounter an array with IndexVar as the index of an +/// ArraySubsriptExpression, note it as a consistent usage and prune the +/// AST traversal. +/// +/// For example, given +/// \code +/// const int N = 5; +/// int arr[N]; +/// \endcode +/// This is intended to permit +/// \code +/// for (int i = 0; i < N; ++i) { /* use arr[i] */ } +/// \endcode +/// but not +/// \code +/// for (int i = 0; i < N; ++i) { /* use notArr[i] */ } +/// \endcode +/// and further checking needs to be done later to ensure that exactly one array +/// is referenced. +bool ForLoopIndexUseVisitor::TraverseArraySubscriptExpr(ArraySubscriptExpr *E) { + Expr *Arr = E->getBase(); + if (!isIndexInSubscriptExpr(E->getIdx(), IndexVar)) + return VisitorBase::TraverseArraySubscriptExpr(E); + + if ((ContainerExpr && + !areSameExpr(Context, Arr->IgnoreParenImpCasts(), + ContainerExpr->IgnoreParenImpCasts())) || + !arrayMatchesBoundExpr(Context, Arr->IgnoreImpCasts()->getType(), + ArrayBoundExpr)) { + // If we have already discovered the array being indexed and this isn't it + // or this array doesn't match, mark this loop as unconvertible. + OnlyUsedAsIndex = false; + return VisitorBase::TraverseArraySubscriptExpr(E); + } + + if (!ContainerExpr) + ContainerExpr = Arr; + + addUsage(Usage(E)); + return true; +} + +/// \brief If we encounter a reference to IndexVar in an unpruned branch of the +/// traversal, mark this loop as unconvertible. +/// +/// This implements the whitelist for convertible loops: any usages of IndexVar +/// not explicitly considered convertible by this traversal will be caught by +/// this function. +/// +/// Additionally, if the container expression is more complex than just a +/// DeclRefExpr, and some part of it is appears elsewhere in the loop, lower +/// our confidence in the transformation. +/// +/// For example, these are not permitted: +/// \code +/// for (int i = 0; i < N; ++i) { printf("arr[%d] = %d", i, arr[i]); } +/// for (vector::iterator i = container.begin(), e = container.end(); +/// i != e; ++i) +/// i.insert(0); +/// for (vector::iterator i = container.begin(), e = container.end(); +/// i != e; ++i) +/// if (i + 1 != e) +/// printf("%d", *i); +/// \endcode +/// +/// And these will raise the risk level: +/// \code +/// int arr[10][20]; +/// int l = 5; +/// for (int j = 0; j < 20; ++j) +/// int k = arr[l][j] + l; // using l outside arr[l] is considered risky +/// for (int i = 0; i < obj.getVector().size(); ++i) +/// obj.foo(10); // using `obj` is considered risky +/// \endcode +bool ForLoopIndexUseVisitor::VisitDeclRefExpr(DeclRefExpr *E) { + const ValueDecl *TheDecl = E->getDecl(); + if (areSameVariable(IndexVar, TheDecl) || + exprReferencesVariable(IndexVar, E) || areSameVariable(EndVar, TheDecl) || + exprReferencesVariable(EndVar, E)) + OnlyUsedAsIndex = false; + if (containsExpr(Context, &DependentExprs, E)) + ConfidenceLevel.lowerTo(Confidence::CL_Risky); + return true; +} + +/// \brief If the loop index is captured by a lambda, replace this capture +/// by the range-for loop variable. +/// +/// For example: +/// \code +/// for (int i = 0; i < N; ++i) { +/// auto f = [v, i](int k) { +/// printf("%d\n", v[i] + k); +/// }; +/// f(v[i]); +/// } +/// \endcode +/// +/// Will be replaced by: +/// \code +/// for (auto & elem : v) { +/// auto f = [v, elem](int k) { +/// printf("%d\n", elem + k); +/// }; +/// f(elem); +/// } +/// \endcode +bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE, + const LambdaCapture *C, + Expr *Init) { + if (C->capturesVariable()) { + const VarDecl *VDecl = C->getCapturedVar(); + if (areSameVariable(IndexVar, cast(VDecl))) { + // FIXME: if the index is captured, it will count as an usage and the + // alias (if any) won't work, because it is only used in case of having + // exactly one usage. + addUsage(Usage(nullptr, + C->getCaptureKind() == LCK_ByCopy ? Usage::UK_CaptureByCopy + : Usage::UK_CaptureByRef, + C->getLocation())); + } + } + return VisitorBase::TraverseLambdaCapture(LE, C, Init); +} + +/// \brief If we find that another variable is created just to refer to the loop +/// element, note it for reuse as the loop variable. +/// +/// See the comments for isAliasDecl. +bool ForLoopIndexUseVisitor::VisitDeclStmt(DeclStmt *S) { + if (!AliasDecl && S->isSingleDecl() && + isAliasDecl(Context, S->getSingleDecl(), IndexVar)) { + AliasDecl = S; + if (CurrStmtParent) { + if (isa(CurrStmtParent) || isa(CurrStmtParent) || + isa(CurrStmtParent)) + ReplaceWithAliasUse = true; + else if (isa(CurrStmtParent)) { + if (cast(CurrStmtParent)->getConditionVariableDeclStmt() == S) + ReplaceWithAliasUse = true; + else + // It's assumed S came the for loop's init clause. + AliasFromForInit = true; + } + } + } + + return true; +} + +bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) { + // If this is an initialization expression for a lambda capture, prune the + // traversal so that we don't end up diagnosing the contained DeclRefExpr as + // inconsistent usage. No need to record the usage here -- this is done in + // TraverseLambdaCapture(). + if (const auto *LE = dyn_cast_or_null(NextStmtParent)) { + // Any child of a LambdaExpr that isn't the body is an initialization + // expression. + if (S != LE->getBody()) { + return true; + } + } + + // All this pointer swapping is a mechanism for tracking immediate parentage + // of Stmts. + const Stmt *OldNextParent = NextStmtParent; + CurrStmtParent = NextStmtParent; + NextStmtParent = S; + bool Result = VisitorBase::TraverseStmt(S); + NextStmtParent = OldNextParent; + return Result; +} + +std::string VariableNamer::createIndexName() { + // FIXME: Add in naming conventions to handle: + // - How to handle conflicts. + // - An interactive process for naming. + std::string IteratorName; + StringRef ContainerName; + if (TheContainer) + ContainerName = TheContainer->getName(); + + size_t Len = ContainerName.size(); + if (Len > 1 && ContainerName.endswith(Style == NS_UpperCase ? "S" : "s")) { + IteratorName = ContainerName.substr(0, Len - 1); + // E.g.: (auto thing : things) + if (!declarationExists(IteratorName) || IteratorName == OldIndex->getName()) + return IteratorName; + } + + if (Len > 2 && ContainerName.endswith(Style == NS_UpperCase ? "S_" : "s_")) { + IteratorName = ContainerName.substr(0, Len - 2); + // E.g.: (auto thing : things_) + if (!declarationExists(IteratorName) || IteratorName == OldIndex->getName()) + return IteratorName; + } + + return OldIndex->getName(); +} + +/// \brief Determines whether or not the the name \a Symbol conflicts with +/// language keywords or defined macros. Also checks if the name exists in +/// LoopContext, any of its parent contexts, or any of its child statements. +/// +/// We also check to see if the same identifier was generated by this loop +/// converter in a loop nested within SourceStmt. +bool VariableNamer::declarationExists(StringRef Symbol) { + assert(Context != nullptr && "Expected an ASTContext"); + IdentifierInfo &Ident = Context->Idents.get(Symbol); + + // Check if the symbol is not an identifier (ie. is a keyword or alias). + if (!isAnyIdentifier(Ident.getTokenID())) + return true; + + // Check for conflicting macro definitions. + if (Ident.hasMacroDefinition()) + return true; + + // Determine if the symbol was generated in a parent context. + for (const Stmt *S = SourceStmt; S != nullptr; S = ReverseAST->lookup(S)) { + StmtGeneratedVarNameMap::const_iterator I = GeneratedDecls->find(S); + if (I != GeneratedDecls->end() && I->second == Symbol) + return true; + } + + // FIXME: Rather than detecting conflicts at their usages, we should check the + // parent context. + // For some reason, lookup() always returns the pair (NULL, NULL) because its + // StoredDeclsMap is not initialized (i.e. LookupPtr.getInt() is false inside + // of DeclContext::lookup()). Why is this? + + // Finally, determine if the symbol was used in the loop or a child context. + DeclFinderASTVisitor DeclFinder(Symbol, GeneratedDecls); + return DeclFinder.findUsages(SourceStmt); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/LoopConvertUtils.h b/clang-tidy/modernize/LoopConvertUtils.h new file mode 100644 index 000000000..bee77d984 --- /dev/null +++ b/clang-tidy/modernize/LoopConvertUtils.h @@ -0,0 +1,467 @@ +//===--- LoopConvertUtils.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include + +namespace clang { +namespace tidy { +namespace modernize { + +enum LoopFixerKind { LFK_Array, LFK_Iterator, LFK_PseudoArray }; + +/// A map used to walk the AST in reverse: maps child Stmt to parent Stmt. +typedef llvm::DenseMap StmtParentMap; + +/// A map used to walk the AST in reverse: +/// maps VarDecl to the to parent DeclStmt. +typedef llvm::DenseMap + DeclParentMap; + +/// A map used to track which variables have been removed by a refactoring pass. +/// It maps the parent ForStmt to the removed index variable's VarDecl. +typedef llvm::DenseMap + ReplacedVarsMap; + +/// A map used to remember the variable names generated in a Stmt +typedef llvm::DenseMap + StmtGeneratedVarNameMap; + +/// A vector used to store the AST subtrees of an Expr. +typedef llvm::SmallVector ComponentVector; + +/// \brief Class used build the reverse AST properties needed to detect +/// name conflicts and free variables. +class StmtAncestorASTVisitor + : public clang::RecursiveASTVisitor { +public: + StmtAncestorASTVisitor() { StmtStack.push_back(nullptr); } + + /// \brief Run the analysis on the TranslationUnitDecl. + /// + /// In case we're running this analysis multiple times, don't repeat the work. + void gatherAncestors(const clang::TranslationUnitDecl *T) { + if (StmtAncestors.empty()) + TraverseDecl(const_cast(T)); + } + + /// Accessor for StmtAncestors. + const StmtParentMap &getStmtToParentStmtMap() { return StmtAncestors; } + + /// Accessor for DeclParents. + const DeclParentMap &getDeclToParentStmtMap() { return DeclParents; } + + friend class clang::RecursiveASTVisitor; + +private: + StmtParentMap StmtAncestors; + DeclParentMap DeclParents; + llvm::SmallVector StmtStack; + + bool TraverseStmt(clang::Stmt *Statement); + bool VisitDeclStmt(clang::DeclStmt *Statement); +}; + +/// Class used to find the variables and member expressions on which an +/// arbitrary expression depends. +class ComponentFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + ComponentFinderASTVisitor() = default; + + /// Find the components of an expression and place them in a ComponentVector. + void findExprComponents(const clang::Expr *SourceExpr) { + TraverseStmt(const_cast(SourceExpr)); + } + + /// Accessor for Components. + const ComponentVector &getComponents() { return Components; } + + friend class clang::RecursiveASTVisitor; + +private: + ComponentVector Components; + + bool VisitDeclRefExpr(clang::DeclRefExpr *E); + bool VisitMemberExpr(clang::MemberExpr *Member); +}; + +/// Class used to determine if an expression is dependent on a variable declared +/// inside of the loop where it would be used. +class DependencyFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + DependencyFinderASTVisitor(const StmtParentMap *StmtParents, + const DeclParentMap *DeclParents, + const ReplacedVarsMap *ReplacedVars, + const clang::Stmt *ContainingStmt) + : StmtParents(StmtParents), DeclParents(DeclParents), + ContainingStmt(ContainingStmt), ReplacedVars(ReplacedVars) {} + + /// \brief Run the analysis on Body, and return true iff the expression + /// depends on some variable declared within ContainingStmt. + /// + /// This is intended to protect against hoisting the container expression + /// outside of an inner context if part of that expression is declared in that + /// inner context. + /// + /// For example, + /// \code + /// const int N = 10, M = 20; + /// int arr[N][M]; + /// int getRow(); + /// + /// for (int i = 0; i < M; ++i) { + /// int k = getRow(); + /// printf("%d:", arr[k][i]); + /// } + /// \endcode + /// At first glance, this loop looks like it could be changed to + /// \code + /// for (int elem : arr[k]) { + /// int k = getIndex(); + /// printf("%d:", elem); + /// } + /// \endcode + /// But this is malformed, since `k` is used before it is defined! + /// + /// In order to avoid this, this class looks at the container expression + /// `arr[k]` and decides whether or not it contains a sub-expression declared + /// within the the loop body. + bool dependsOnInsideVariable(const clang::Stmt *Body) { + DependsOnInsideVariable = false; + TraverseStmt(const_cast(Body)); + return DependsOnInsideVariable; + } + + friend class clang::RecursiveASTVisitor; + +private: + const StmtParentMap *StmtParents; + const DeclParentMap *DeclParents; + const clang::Stmt *ContainingStmt; + const ReplacedVarsMap *ReplacedVars; + bool DependsOnInsideVariable; + + bool VisitVarDecl(clang::VarDecl *V); + bool VisitDeclRefExpr(clang::DeclRefExpr *D); +}; + +/// Class used to determine if any declarations used in a Stmt would conflict +/// with a particular identifier. This search includes the names that don't +/// actually appear in the AST (i.e. created by a refactoring tool) by including +/// a map from Stmts to generated names associated with those stmts. +class DeclFinderASTVisitor + : public clang::RecursiveASTVisitor { +public: + DeclFinderASTVisitor(const std::string &Name, + const StmtGeneratedVarNameMap *GeneratedDecls) + : Name(Name), GeneratedDecls(GeneratedDecls), Found(false) {} + + /// Attempts to find any usages of variables name Name in Body, returning + /// true when it is used in Body. This includes the generated loop variables + /// of ForStmts which have already been transformed. + bool findUsages(const clang::Stmt *Body) { + Found = false; + TraverseStmt(const_cast(Body)); + return Found; + } + + friend class clang::RecursiveASTVisitor; + +private: + std::string Name; + /// GeneratedDecls keeps track of ForStmts which have been transformed, + /// mapping each modified ForStmt to the variable generated in the loop. + const StmtGeneratedVarNameMap *GeneratedDecls; + bool Found; + + bool VisitForStmt(clang::ForStmt *F); + bool VisitNamedDecl(clang::NamedDecl *D); + bool VisitDeclRefExpr(clang::DeclRefExpr *D); + bool VisitTypeLoc(clang::TypeLoc TL); +}; + +/// \brief The information needed to describe a valid convertible usage +/// of an array index or iterator. +struct Usage { + enum UsageKind { + // Regular usages of the loop index (the ones not specified below). Some + // examples: + // \code + // int X = 8 * Arr[i]; + // ^~~~~~ + // f(param1, param2, *It); + // ^~~ + // if (Vec[i].SomeBool) {} + // ^~~~~~ + // \endcode + UK_Default, + // Indicates whether this is an access to a member through the arrow + // operator on pointers or iterators. + UK_MemberThroughArrow, + // If the variable is being captured by a lambda, indicates whether the + // capture was done by value or by reference. + UK_CaptureByCopy, + UK_CaptureByRef + }; + // The expression that is going to be converted. Null in case of lambda + // captures. + const Expr *Expression; + + UsageKind Kind; + + // Range that covers this usage. + SourceRange Range; + + explicit Usage(const Expr *E) + : Expression(E), Kind(UK_Default), Range(Expression->getSourceRange()) {} + Usage(const Expr *E, UsageKind Kind, SourceRange Range) + : Expression(E), Kind(Kind), Range(std::move(Range)) {} +}; + +/// \brief A class to encapsulate lowering of the tool's confidence level. +class Confidence { +public: + enum Level { + // Transformations that are likely to change semantics. + CL_Risky, + + // Transformations that might change semantics. + CL_Reasonable, + + // Transformations that will not change semantics. + CL_Safe + }; + /// \brief Initialize confidence level. + explicit Confidence(Confidence::Level Level) : CurrentLevel(Level) {} + + /// \brief Lower the internal confidence level to Level, but do not raise it. + void lowerTo(Confidence::Level Level) { + CurrentLevel = std::min(Level, CurrentLevel); + } + + /// \brief Return the internal confidence level. + Level getLevel() const { return CurrentLevel; } + +private: + Level CurrentLevel; +}; + +// The main computational result of ForLoopIndexVisitor. +typedef llvm::SmallVector UsageResult; + +// General functions used by ForLoopIndexUseVisitor and LoopConvertCheck. +const Expr *digThroughConstructors(const Expr *E); +bool areSameExpr(ASTContext *Context, const Expr *First, const Expr *Second); +const DeclRefExpr *getDeclRef(const Expr *E); +bool areSameVariable(const ValueDecl *First, const ValueDecl *Second); + +/// \brief Discover usages of expressions consisting of index or iterator +/// access. +/// +/// Given an index variable, recursively crawls a for loop to discover if the +/// index variable is used in a way consistent with range-based for loop access. +class ForLoopIndexUseVisitor + : public RecursiveASTVisitor { +public: + ForLoopIndexUseVisitor(ASTContext *Context, const VarDecl *IndexVar, + const VarDecl *EndVar, const Expr *ContainerExpr, + const Expr *ArrayBoundExpr, + bool ContainerNeedsDereference); + + /// \brief Finds all uses of IndexVar in Body, placing all usages in Usages, + /// and returns true if IndexVar was only used in a way consistent with a + /// range-based for loop. + /// + /// The general strategy is to reject any DeclRefExprs referencing IndexVar, + /// with the exception of certain acceptable patterns. + /// For arrays, the DeclRefExpr for IndexVar must appear as the index of an + /// ArraySubscriptExpression. Iterator-based loops may dereference + /// IndexVar or call methods through operator-> (builtin or overloaded). + /// Array-like containers may use IndexVar as a parameter to the at() member + /// function and in overloaded operator[]. + bool findAndVerifyUsages(const Stmt *Body); + + /// \brief Add a set of components that we should consider relevant to the + /// container. + void addComponents(const ComponentVector &Components); + + /// \brief Accessor for Usages. + const UsageResult &getUsages() const { return Usages; } + + /// \brief Adds the Usage if it was not added before. + void addUsage(const Usage &U); + + /// \brief Get the container indexed by IndexVar, if any. + const Expr *getContainerIndexed() const { return ContainerExpr; } + + /// \brief Returns the statement declaring the variable created as an alias + /// for the loop element, if any. + const DeclStmt *getAliasDecl() const { return AliasDecl; } + + /// \brief Accessor for ConfidenceLevel. + Confidence::Level getConfidenceLevel() const { + return ConfidenceLevel.getLevel(); + } + + /// \brief Indicates if the alias declaration was in a place where it cannot + /// simply be removed but rather replaced with a use of the alias variable. + /// For example, variables declared in the condition of an if, switch, or for + /// stmt. + bool aliasUseRequired() const { return ReplaceWithAliasUse; } + + /// \brief Indicates if the alias declaration came from the init clause of a + /// nested for loop. SourceRanges provided by Clang for DeclStmts in this + /// case need to be adjusted. + bool aliasFromForInit() const { return AliasFromForInit; } + +private: + /// Typedef used in CRTP functions. + typedef RecursiveASTVisitor VisitorBase; + friend class RecursiveASTVisitor; + + /// Overriden methods for RecursiveASTVisitor's traversal. + bool TraverseArraySubscriptExpr(ArraySubscriptExpr *E); + bool TraverseCXXMemberCallExpr(CXXMemberCallExpr *MemberCall); + bool TraverseCXXOperatorCallExpr(CXXOperatorCallExpr *OpCall); + bool TraverseLambdaCapture(LambdaExpr *LE, const LambdaCapture *C, + Expr *Init); + bool TraverseMemberExpr(MemberExpr *Member); + bool TraverseUnaryDeref(UnaryOperator *Uop); + bool VisitDeclRefExpr(DeclRefExpr *E); + bool VisitDeclStmt(DeclStmt *S); + bool TraverseStmt(Stmt *S); + + /// \brief Add an expression to the list of expressions on which the container + /// expression depends. + void addComponent(const Expr *E); + + // Input member variables: + ASTContext *Context; + /// The index variable's VarDecl. + const VarDecl *IndexVar; + /// The loop's 'end' variable, which cannot be mentioned at all. + const VarDecl *EndVar; + /// The Expr which refers to the container. + const Expr *ContainerExpr; + /// The Expr which refers to the terminating condition for array-based loops. + const Expr *ArrayBoundExpr; + bool ContainerNeedsDereference; + + // Output member variables: + /// A container which holds all usages of IndexVar as the index of + /// ArraySubscriptExpressions. + UsageResult Usages; + llvm::SmallSet UsageLocations; + bool OnlyUsedAsIndex; + /// The DeclStmt for an alias to the container element. + const DeclStmt *AliasDecl; + Confidence ConfidenceLevel; + /// \brief A list of expressions on which ContainerExpr depends. + /// + /// If any of these expressions are encountered outside of an acceptable usage + /// of the loop element, lower our confidence level. + llvm::SmallVector, 16> + DependentExprs; + + /// The parent-in-waiting. Will become the real parent once we traverse down + /// one level in the AST. + const Stmt *NextStmtParent; + /// The actual parent of a node when Visit*() calls are made. Only the + /// parentage of DeclStmt's to possible iteration/selection statements is of + /// importance. + const Stmt *CurrStmtParent; + + /// \see aliasUseRequired(). + bool ReplaceWithAliasUse; + /// \see aliasFromForInit(). + bool AliasFromForInit; +}; + +struct TUTrackingInfo { + /// \brief Reset and initialize per-TU tracking information. + /// + /// Must be called before using container accessors. + TUTrackingInfo() : ParentFinder(new StmtAncestorASTVisitor) {} + + StmtAncestorASTVisitor &getParentFinder() { return *ParentFinder; } + StmtGeneratedVarNameMap &getGeneratedDecls() { return GeneratedDecls; } + ReplacedVarsMap &getReplacedVars() { return ReplacedVars; } + +private: + std::unique_ptr ParentFinder; + StmtGeneratedVarNameMap GeneratedDecls; + ReplacedVarsMap ReplacedVars; +}; + +/// \brief Create names for generated variables within a particular statement. +/// +/// VariableNamer uses a DeclContext as a reference point, checking for any +/// conflicting declarations higher up in the context or within SourceStmt. +/// It creates a variable name using hints from a source container and the old +/// index, if they exist. +class VariableNamer { +public: + // Supported naming styles. + enum NamingStyle { + NS_CamelBack, + NS_CamelCase, + NS_LowerCase, + NS_UpperCase, + }; + + VariableNamer(StmtGeneratedVarNameMap *GeneratedDecls, + const StmtParentMap *ReverseAST, const clang::Stmt *SourceStmt, + const clang::VarDecl *OldIndex, + const clang::ValueDecl *TheContainer, + const clang::ASTContext *Context, NamingStyle Style) + : GeneratedDecls(GeneratedDecls), ReverseAST(ReverseAST), + SourceStmt(SourceStmt), OldIndex(OldIndex), TheContainer(TheContainer), + Context(Context), Style(Style) {} + + /// \brief Generate a new index name. + /// + /// Generates the name to be used for an inserted iterator. It relies on + /// declarationExists() to determine that there are no naming conflicts, and + /// tries to use some hints from the container name and the old index name. + std::string createIndexName(); + +private: + StmtGeneratedVarNameMap *GeneratedDecls; + const StmtParentMap *ReverseAST; + const clang::Stmt *SourceStmt; + const clang::VarDecl *OldIndex; + const clang::ValueDecl *TheContainer; + const clang::ASTContext *Context; + const NamingStyle Style; + + // Determine whether or not a declaration that would conflict with Symbol + // exists in an outer context or in any statement contained in SourceStmt. + bool declarationExists(llvm::StringRef Symbol); +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_UTILS_H diff --git a/clang-tidy/modernize/MakeSharedCheck.cpp b/clang-tidy/modernize/MakeSharedCheck.cpp new file mode 100644 index 000000000..8d3020c6d --- /dev/null +++ b/clang-tidy/modernize/MakeSharedCheck.cpp @@ -0,0 +1,31 @@ +//===--- MakeSharedCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MakeSharedCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +MakeSharedCheck::MakeSharedCheck(StringRef Name, ClangTidyContext *Context) + : MakeSmartPtrCheck(Name, Context, "std::make_shared") {} + +MakeSharedCheck::SmartPtrTypeMatcher +MakeSharedCheck::getSmartPointerTypeMatcher() const { + return qualType(hasDeclaration(classTemplateSpecializationDecl( + matchesName("::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..d032588cf --- /dev/null +++ b/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -0,0 +1,206 @@ +//===--- 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/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +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, + std::string makeSmartPtrFunctionName) + : ClangTidyCheck(Name, Context), + makeSmartPtrFunctionName(std::move(makeSmartPtrFunctionName)) {} + +void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // Calling make_smart_ptr from within a member function of a type with a + // private or protected constructor would be ill-formed. + auto CanCallCtor = unless(has(ignoringImpCasts( + cxxConstructExpr(hasDeclaration(decl(unless(isPublic()))))))); + + Finder->addMatcher( + cxxBindTemporaryExpr(has(ignoringParenImpCasts( + cxxConstructExpr( + hasType(getSmartPointerTypeMatcher()), argumentCountIs(1), + hasArgument(0, + cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType( + equalsBoundNode(PointerType))))), + CanCallCtor) + .bind(NewExpression))) + .bind(ConstructorCall)))), + this); + + Finder->addMatcher( + cxxMemberCallExpr( + thisPointerType(getSmartPointerTypeMatcher()), + callee(cxxMethodDecl(hasName("reset"))), + hasArgument(0, cxxNewExpr(CanCallCtor).bind(NewExpression))) + .bind(ResetCall), + this); +} + +void MakeSmartPtrCheck::check(const MatchFinder::MatchResult &Result) { + // 'smart_ptr' refers to 'std::shared_ptr' or 'std::unique_ptr' or other + // pointer, 'make_smart_ptr' refers to 'std::make_shared' or + // 'std::make_unique' or other function that creates smart_ptr. + + SourceManager &SM = *Result.SourceManager; + const auto *Construct = + Result.Nodes.getNodeAs(ConstructorCall); + const auto *Reset = Result.Nodes.getNodeAs(ResetCall); + const auto *Type = Result.Nodes.getNodeAs(PointerType); + const auto *New = Result.Nodes.getNodeAs(NewExpression); + + if (New->getNumPlacementArgs() != 0) + return; + + if (Construct) + checkConstruct(SM, Construct, Type, New); + else if (Reset) + checkReset(SM, Reset, New); +} + +void MakeSmartPtrCheck::checkConstruct(SourceManager &SM, + const CXXConstructExpr *Construct, + const QualType *Type, + const CXXNewExpr *New) { + SourceLocation ConstructCallStart = Construct->getExprLoc(); + + bool Invalid = false; + StringRef ExprStr = Lexer::getSourceText( + CharSourceRange::getCharRange( + ConstructCallStart, Construct->getParenOrBraceRange().getBegin()), + SM, LangOptions(), &Invalid); + if (Invalid) + return; + + auto Diag = diag(ConstructCallStart, "use %0 instead") + << makeSmartPtrFunctionName; + + // Find the location of the template's left angle. + size_t LAngle = ExprStr.find("<"); + SourceLocation ConstructCallEnd; + if (LAngle == StringRef::npos) { + // If the template argument is missing (because it is part of the alias) + // we have to add it back. + ConstructCallEnd = ConstructCallStart.getLocWithOffset(ExprStr.size()); + Diag << FixItHint::CreateInsertion( + ConstructCallEnd, "<" + Type->getAsString(getLangOpts()) + ">"); + } else { + ConstructCallEnd = ConstructCallStart.getLocWithOffset(LAngle); + } + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(ConstructCallStart, ConstructCallEnd), + makeSmartPtrFunctionName); + + // If the smart_ptr is built with brace enclosed direct initialization, use + // parenthesis instead. + if (Construct->isListInitialization()) { + SourceRange BraceRange = Construct->getParenOrBraceRange(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange( + BraceRange.getBegin(), BraceRange.getBegin().getLocWithOffset(1)), + "("); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(BraceRange.getEnd(), + BraceRange.getEnd().getLocWithOffset(1)), + ")"); + } + + replaceNew(Diag, New); +} + +void MakeSmartPtrCheck::checkReset(SourceManager &SM, + const CXXMemberCallExpr *Reset, + const CXXNewExpr *New) { + const auto *Expr = cast(Reset->getCallee()); + SourceLocation OperatorLoc = Expr->getOperatorLoc(); + SourceLocation ResetCallStart = Reset->getExprLoc(); + SourceLocation ExprStart = Expr->getLocStart(); + SourceLocation ExprEnd = + Lexer::getLocForEndOfToken(Expr->getLocEnd(), 0, SM, getLangOpts()); + + auto Diag = diag(ResetCallStart, "use %0 instead") + << makeSmartPtrFunctionName; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OperatorLoc, ExprEnd), + (llvm::Twine(" = ") + makeSmartPtrFunctionName + "<" + + New->getAllocatedType().getAsString(getLangOpts()) + ">") + .str()); + + if (Expr->isArrow()) + Diag << FixItHint::CreateInsertion(ExprStart, "*"); + + replaceNew(Diag, New); +} + +void MakeSmartPtrCheck::replaceNew(DiagnosticBuilder &Diag, + const CXXNewExpr *New) { + SourceLocation NewStart = New->getSourceRange().getBegin(); + SourceLocation NewEnd = New->getSourceRange().getEnd(); + switch (New->getInitializationStyle()) { + case CXXNewExpr::NoInit: { + Diag << FixItHint::CreateRemoval(SourceRange(NewStart, NewEnd)); + break; + } + case CXXNewExpr::CallInit: { + SourceRange InitRange = New->getDirectInitRange(); + Diag << FixItHint::CreateRemoval( + SourceRange(NewStart, InitRange.getBegin())); + Diag << FixItHint::CreateRemoval(SourceRange(InitRange.getEnd(), NewEnd)); + break; + } + case CXXNewExpr::ListInit: { + // Range of the substring that we do not want to remove. + SourceRange InitRange; + if (const auto *NewConstruct = New->getConstructExpr()) { + // Direct initialization with initialization list. + // struct S { S(int x) {} }; + // smart_ptr(new S{5}); + // The arguments in the initialization list are going to be forwarded to + // the constructor, so this has to be replaced with: + // struct S { S(int x) {} }; + // std::make_smart_ptr(5); + InitRange = SourceRange( + NewConstruct->getParenOrBraceRange().getBegin().getLocWithOffset(1), + NewConstruct->getParenOrBraceRange().getEnd().getLocWithOffset(-1)); + } else { + // Aggregate initialization. + // smart_ptr(new Pair{first, second}); + // Has to be replaced with: + // smart_ptr(Pair{first, second}); + InitRange = SourceRange( + New->getAllocatedTypeSourceInfo()->getTypeLoc().getLocStart(), + New->getInitializer()->getSourceRange().getEnd()); + } + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(NewStart, InitRange.getBegin())); + Diag << FixItHint::CreateRemoval( + SourceRange(InitRange.getEnd().getLocWithOffset(1), NewEnd)); + break; + } + } +} + +} // 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..87352e2eb --- /dev/null +++ b/clang-tidy/modernize/MakeSmartPtrCheck.h @@ -0,0 +1,59 @@ +//===--- 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 "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, + std::string makeSmartPtrFunctionName); + void registerMatchers(ast_matchers::MatchFinder *Finder) final; + void check(const ast_matchers::MatchFinder::MatchResult &Result) final; + +protected: + using SmartPtrTypeMatcher = ast_matchers::internal::BindableMatcher; + + /// Returns matcher that match with different smart pointer types. + /// + /// Requires to bind pointer type (qualType) with PointerType string declared + /// in this class. + virtual SmartPtrTypeMatcher getSmartPointerTypeMatcher() const = 0; + + static const char PointerType[]; + static const char ConstructorCall[]; + static const char ResetCall[]; + static const char NewExpression[]; + +private: + std::string makeSmartPtrFunctionName; + + void checkConstruct(SourceManager &SM, const CXXConstructExpr *Construct, + const QualType *Type, const CXXNewExpr *New); + void checkReset(SourceManager &SM, const CXXMemberCallExpr *Member, + const CXXNewExpr *New); + void replaceNew(DiagnosticBuilder &Diag, const CXXNewExpr *New); +}; + +} // 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..cef8e9d9a --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.cpp @@ -0,0 +1,40 @@ +//===--- MakeUniqueCheck.cpp - clang-tidy----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MakeUniqueCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +MakeUniqueCheck::MakeUniqueCheck(StringRef Name, + clang::tidy::ClangTidyContext *Context) + : MakeSmartPtrCheck(Name, Context, "std::make_unique") {} + +MakeUniqueCheck::SmartPtrTypeMatcher +MakeUniqueCheck::getSmartPointerTypeMatcher() const { + return qualType(hasDeclaration(classTemplateSpecializationDecl( + matchesName("::std::unique_ptr"), templateArgumentCountIs(2), + hasTemplateArgument( + 0, templateArgument(refersToType(qualType().bind(PointerType)))), + hasTemplateArgument( + 1, templateArgument(refersToType( + qualType(hasDeclaration(classTemplateSpecializationDecl( + matchesName("::std::default_delete"), + templateArgumentCountIs(1), + hasTemplateArgument( + 0, templateArgument(refersToType(qualType( + equalsBoundNode(PointerType)))))))))))))); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/MakeUniqueCheck.h b/clang-tidy/modernize/MakeUniqueCheck.h new file mode 100644 index 000000000..15fbd5560 --- /dev/null +++ b/clang-tidy/modernize/MakeUniqueCheck.h @@ -0,0 +1,40 @@ +//===--- MakeUniqueCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H + +#include "MakeSmartPtrCheck.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace the pattern: +/// \code +/// std::unique_ptr(new type(args...)) +/// \endcode +/// +/// With the C++14 version: +/// \code +/// std::make_unique(args...) +/// \endcode +class MakeUniqueCheck : public MakeSmartPtrCheck { +public: + MakeUniqueCheck(StringRef Name, ClangTidyContext *Context); + +protected: + SmartPtrTypeMatcher getSmartPointerTypeMatcher() const override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_MAKE_UNIQUE_H diff --git a/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tidy/modernize/ModernizeTidyModule.cpp new file mode 100644 index 000000000..3274f5d60 --- /dev/null +++ b/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -0,0 +1,102 @@ +//===--- 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 "ShrinkToFitCheck.h" +#include "UseAutoCheck.h" +#include "UseBoolLiteralsCheck.h" +#include "UseDefaultMemberInitCheck.h" +#include "UseEmplaceCheck.h" +#include "UseEqualsDefaultCheck.h" +#include "UseEqualsDeleteCheck.h" +#include "UseNullptrCheck.h" +#include "UseOverrideCheck.h" +#include "UseTransparentFunctorsCheck.h" +#include "UseUsingCheck.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +class ModernizeModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("modernize-avoid-bind"); + CheckFactories.registerCheck( + "modernize-deprecated-headers"); + CheckFactories.registerCheck("modernize-loop-convert"); + CheckFactories.registerCheck("modernize-make-shared"); + CheckFactories.registerCheck("modernize-make-unique"); + CheckFactories.registerCheck("modernize-pass-by-value"); + CheckFactories.registerCheck( + "modernize-raw-string-literal"); + CheckFactories.registerCheck( + "modernize-redundant-void-arg"); + CheckFactories.registerCheck( + "modernize-replace-auto-ptr"); + CheckFactories.registerCheck("modernize-shrink-to-fit"); + 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-nullptr"); + CheckFactories.registerCheck("modernize-use-override"); + CheckFactories.registerCheck( + "modernize-use-transparent-functors"); + CheckFactories.registerCheck("modernize-use-using"); + } + + ClangTidyOptions getModuleOptions() override { + ClangTidyOptions Options; + auto &Opts = Options.CheckOptions; + // For types whose size in bytes is above this threshold, we prefer taking a + // const-reference than making a copy. + Opts["modernize-loop-convert.MaxCopySize"] = "16"; + + Opts["modernize-loop-convert.MinConfidence"] = "reasonable"; + Opts["modernize-loop-convert.NamingStyle"] = "CamelCase"; + Opts["modernize-pass-by-value.IncludeStyle"] = "llvm"; // Also: "google". + Opts["modernize-replace-auto-ptr.IncludeStyle"] = "llvm"; // Also: "google". + + // Comma-separated list of macros that behave like NULL. + Opts["modernize-use-nullptr.NullMacros"] = "NULL"; + return Options; + } +}; + +// Register the ModernizeTidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add X("modernize-module", + "Add modernize checks."); + +} // namespace modernize + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ModernizeModule. +volatile int ModernizeModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tidy/modernize/PassByValueCheck.cpp new file mode 100644 index 000000000..2d462dbb4 --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.cpp @@ -0,0 +1,232 @@ +//===--- PassByValueCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "PassByValueCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; +using namespace llvm; + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Matches move-constructible classes. +/// +/// Given +/// \code +/// // POD types are trivially move constructible. +/// struct Foo { int a; }; +/// +/// struct Bar { +/// Bar(Bar &&) = deleted; +/// int a; +/// }; +/// \endcode +/// recordDecl(isMoveConstructible()) +/// matches "Foo". +AST_MATCHER(CXXRecordDecl, isMoveConstructible) { + for (const CXXConstructorDecl *Ctor : Node.ctors()) { + if (Ctor->isMoveConstructor() && !Ctor->isDeleted()) + return true; + } + return false; +} + +static TypeMatcher constRefType() { + return lValueReferenceType(pointee(isConstQualified())); +} + +static TypeMatcher nonConstValueType() { + return qualType(unless(anyOf(referenceType(), isConstQualified()))); +} + +/// \brief Whether or not \p ParamDecl is used exactly one time in \p Ctor. +/// +/// Checks both in the init-list and the body of the constructor. +static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor, + const ParmVarDecl *ParamDecl) { + /// \brief \c clang::RecursiveASTVisitor that checks that the given + /// \c ParmVarDecl is used exactly one time. + /// + /// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn() + class ExactlyOneUsageVisitor + : public RecursiveASTVisitor { + friend class RecursiveASTVisitor; + + public: + ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl) + : ParamDecl(ParamDecl) {} + + /// \brief Whether or not the parameter variable is referred only once in + /// the + /// given constructor. + bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) { + Count = 0; + TraverseDecl(const_cast(Ctor)); + return Count == 1; + } + + private: + /// \brief Counts the number of references to a variable. + /// + /// Stops the AST traversal if more than one usage is found. + bool VisitDeclRefExpr(DeclRefExpr *D) { + if (const ParmVarDecl *To = dyn_cast(D->getDecl())) { + if (To == ParamDecl) { + ++Count; + if (Count > 1) { + // No need to look further, used more than once. + return false; + } + } + } + return true; + } + + const ParmVarDecl *ParamDecl; + unsigned Count; + }; + + return ExactlyOneUsageVisitor(ParamDecl).hasExactlyOneUsageIn(Ctor); +} + +/// \brief Find all references to \p ParamDecl across all of the +/// redeclarations of \p Ctor. +static SmallVector +collectParamDecls(const CXXConstructorDecl *Ctor, + const ParmVarDecl *ParamDecl) { + SmallVector Results; + unsigned ParamIdx = ParamDecl->getFunctionScopeIndex(); + + for (const FunctionDecl *Redecl : Ctor->redecls()) + Results.push_back(Redecl->getParamDecl(ParamIdx)); + return Results; +} + +PassByValueCheck::PassByValueCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))), + ValuesOnly(Options.get("ValuesOnly", 0) != 0) {} + +void PassByValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); + Options.store(Opts, "ValuesOnly", ValuesOnly); +} + +void PassByValueCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxConstructorDecl( + forEachConstructorInitializer( + cxxCtorInitializer( + unless(isBaseInitializer()), + // Clang builds a CXXConstructExpr only when it knows which + // constructor will be called. In dependent contexts a + // ParenListExpr is generated instead of a CXXConstructExpr, + // filtering out templates automatically for us. + withInitializer(cxxConstructExpr( + has(ignoringParenImpCasts(declRefExpr(to( + parmVarDecl( + hasType(qualType( + // Match only const-ref or a non-const value + // parameters. Rvalues and const-values + // shouldn't be modified. + ValuesOnly ? nonConstValueType() + : anyOf(constRefType(), + nonConstValueType())))) + .bind("Param"))))), + hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), unless(isDeleted()), + hasDeclContext( + cxxRecordDecl(isMoveConstructible()))))))) + .bind("Initializer"))) + .bind("Ctor"), + this); +} + +void PassByValueCheck::registerPPCallbacks(CompilerInstance &Compiler) { + // Only register the preprocessor callbacks for C++; the functionality + // currently does not provide any benefit to other languages, despite being + // benign. + if (getLangOpts().CPlusPlus) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); + } +} + +void PassByValueCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Ctor = Result.Nodes.getNodeAs("Ctor"); + const auto *ParamDecl = Result.Nodes.getNodeAs("Param"); + const auto *Initializer = + Result.Nodes.getNodeAs("Initializer"); + SourceManager &SM = *Result.SourceManager; + + // If the parameter is used or anything other than the copy, do not apply + // the changes. + if (!paramReferredExactlyOnce(Ctor, ParamDecl)) + return; + + // If the parameter is trivial to copy, don't move it. Moving a trivivally + // copyable type will cause a problem with misc-move-const-arg + if (ParamDecl->getType().getNonReferenceType().isTriviallyCopyableType( + *Result.Context)) + return; + + auto Diag = diag(ParamDecl->getLocStart(), "pass by value and use std::move"); + + // Iterate over all declarations of the constructor. + for (const ParmVarDecl *ParmDecl : collectParamDecls(Ctor, ParamDecl)) { + auto ParamTL = ParmDecl->getTypeSourceInfo()->getTypeLoc(); + auto RefTL = ParamTL.getAs(); + + // Do not replace if it is already a value, skip. + if (RefTL.isNull()) + continue; + + TypeLoc ValueTL = RefTL.getPointeeLoc(); + auto TypeRange = CharSourceRange::getTokenRange(ParmDecl->getLocStart(), + ParamTL.getLocEnd()); + std::string ValueStr = Lexer::getSourceText(CharSourceRange::getTokenRange( + ValueTL.getSourceRange()), + SM, getLangOpts()) + .str(); + ValueStr += ' '; + Diag << FixItHint::CreateReplacement(TypeRange, ValueStr); + } + + // Use std::move in the initialization list. + Diag << FixItHint::CreateInsertion(Initializer->getRParenLoc(), ")") + << FixItHint::CreateInsertion( + Initializer->getLParenLoc().getLocWithOffset(1), "std::move("); + + if (auto IncludeFixit = Inserter->CreateIncludeInsertion( + Result.SourceManager->getFileID(Initializer->getSourceLocation()), + "utility", + /*IsAngled=*/true)) { + Diag << *IncludeFixit; + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/PassByValueCheck.h b/clang-tidy/modernize/PassByValueCheck.h new file mode 100644 index 000000000..37deb3f70 --- /dev/null +++ b/clang-tidy/modernize/PassByValueCheck.h @@ -0,0 +1,40 @@ +//===--- PassByValueCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +#include + +namespace clang { +namespace tidy { +namespace modernize { + +class PassByValueCheck : public ClangTidyCheck { +public: + PassByValueCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerPPCallbacks(clang::CompilerInstance &Compiler) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + const bool ValuesOnly; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_PASS_BY_VALUE_H diff --git a/clang-tidy/modernize/RawStringLiteralCheck.cpp b/clang-tidy/modernize/RawStringLiteralCheck.cpp new file mode 100644 index 000000000..452c3dd87 --- /dev/null +++ b/clang-tidy/modernize/RawStringLiteralCheck.cpp @@ -0,0 +1,141 @@ +//===--- RawStringLiteralCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RawStringLiteralCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +namespace { + +bool containsEscapes(StringRef HayStack, StringRef Escapes) { + size_t BackSlash = HayStack.find('\\'); + if (BackSlash == StringRef::npos) + return false; + + while (BackSlash != StringRef::npos) { + if (Escapes.find(HayStack[BackSlash + 1]) == StringRef::npos) + return false; + BackSlash = HayStack.find('\\', BackSlash + 2); + } + + return true; +} + +bool isRawStringLiteral(StringRef Text) { + // Already a raw string literal if R comes before ". + const size_t QuotePos = Text.find('"'); + assert(QuotePos != StringRef::npos); + return (QuotePos > 0) && (Text[QuotePos - 1] == 'R'); +} + +bool containsEscapedCharacters(const MatchFinder::MatchResult &Result, + const StringLiteral *Literal) { + // FIXME: Handle L"", u8"", u"" and U"" literals. + if (!Literal->isAscii()) + return false; + + StringRef Bytes = Literal->getBytes(); + // Non-printing characters disqualify this literal: + // \007 = \a bell + // \010 = \b backspace + // \011 = \t horizontal tab + // \012 = \n new line + // \013 = \v vertical tab + // \014 = \f form feed + // \015 = \r carriage return + // \177 = delete + if (Bytes.find_first_of(StringRef("\000\001\002\003\004\005\006\a" + "\b\t\n\v\f\r\016\017" + "\020\021\022\023\024\025\026\027" + "\030\031\032\033\034\035\036\037" + "\177", + 33)) != StringRef::npos) + return false; + + CharSourceRange CharRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(Literal->getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); + StringRef Text = Lexer::getSourceText(CharRange, *Result.SourceManager, + Result.Context->getLangOpts()); + if (isRawStringLiteral(Text)) + return false; + + return containsEscapes(Text, R"('\"?x01)"); +} + +bool containsDelimiter(StringRef Bytes, const std::string &Delimiter) { + return Bytes.find(Delimiter.empty() + ? std::string(R"lit()")lit") + : (")" + Delimiter + R"(")")) != StringRef::npos; +} + +std::string asRawStringLiteral(const StringLiteral *Literal, + const std::string &DelimiterStem) { + const StringRef Bytes = Literal->getBytes(); + std::string Delimiter; + for (int I = 0; containsDelimiter(Bytes, Delimiter); ++I) { + Delimiter = (I == 0) ? DelimiterStem : DelimiterStem + std::to_string(I); + } + + if (Delimiter.empty()) + return (R"(R"()" + Bytes + R"lit()")lit").str(); + + return (R"(R")" + Delimiter + "(" + Bytes + ")" + Delimiter + R"(")").str(); +} + +} // namespace + +RawStringLiteralCheck::RawStringLiteralCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + DelimiterStem(Options.get("DelimiterStem", "lit")) {} + +void RawStringLiteralCheck::storeOptions(ClangTidyOptions::OptionMap &Options) { + ClangTidyCheck::storeOptions(Options); +} + +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)) + replaceWithRawStringLiteral(Result, Literal); +} + +void RawStringLiteralCheck::replaceWithRawStringLiteral( + const MatchFinder::MatchResult &Result, const StringLiteral *Literal) { + 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, asRawStringLiteral(Literal, DelimiterStem)); +} + +} // 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..1a1e135b3 --- /dev/null +++ b/clang-tidy/modernize/RawStringLiteralCheck.h @@ -0,0 +1,44 @@ +//===--- RawStringLiteralCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RAW_STRING_LITERAL_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_RAW_STRING_LITERAL_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check replaces string literals with escaped characters to +/// raw string literals. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-raw-string-literal.html +class RawStringLiteralCheck : public ClangTidyCheck { +public: + RawStringLiteralCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void replaceWithRawStringLiteral( + const ast_matchers::MatchFinder::MatchResult &Result, + const StringLiteral *Literal); + + std::string DelimiterStem; +}; + +} // 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..df6dadc50 --- /dev/null +++ b/clang-tidy/modernize/ReplaceAutoPtrCheck.cpp @@ -0,0 +1,271 @@ +//===--- ReplaceAutoPtrCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ReplaceAutoPtrCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang; +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char AutoPtrTokenId[] = "AutoPrTokenId"; +static const char AutoPtrOwnershipTransferId[] = "AutoPtrOwnershipTransferId"; + +/// \brief Matches expressions that are lvalues. +/// +/// In the following example, a[0] matches expr(isLValue()): +/// \code +/// std::string a[2]; +/// std::string b; +/// b = a[0]; +/// b = "this string won't match"; +/// \endcode +AST_MATCHER(Expr, isLValue) { return Node.getValueKind() == VK_LValue; } + +/// Matches declarations whose declaration context is the C++ standard library +/// namespace std. +/// +/// Note that inline namespaces are silently ignored during the lookup since +/// both libstdc++ and libc++ are known to use them for versioning purposes. +/// +/// Given: +/// \code +/// namespace ns { +/// struct my_type {}; +/// using namespace std; +/// } +/// +/// using std::vector; +/// using ns:my_type; +/// using ns::list; +/// \code +/// +/// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace()))) +/// matches "using std::vector" and "using ns::list". +AST_MATCHER(Decl, isFromStdNamespace) { + const DeclContext *D = Node.getDeclContext(); + + while (D->isInlineNamespace()) + D = D->getParent(); + + if (!D->isNamespace() || !D->getParent()->isTranslationUnit()) + return false; + + const IdentifierInfo *Info = cast(D)->getIdentifier(); + + return (Info && Info->isStr("std")); +} + +/// \brief Matcher that finds auto_ptr declarations. +static DeclarationMatcher AutoPtrDecl = + recordDecl(hasName("auto_ptr"), isFromStdNamespace()); + +/// \brief Matches types declared as auto_ptr. +static TypeMatcher AutoPtrType = qualType(hasDeclaration(AutoPtrDecl)); + +/// \brief Matcher that finds expressions that are candidates to be wrapped with +/// 'std::move'. +/// +/// Binds the id \c AutoPtrOwnershipTransferId to the expression. +static StatementMatcher MovableArgumentMatcher = + expr(allOf(isLValue(), hasType(AutoPtrType))) + .bind(AutoPtrOwnershipTransferId); + +/// \brief Creates a matcher that finds the locations of types referring to the +/// \c std::auto_ptr() type. +/// +/// \code +/// std::auto_ptr a; +/// ^~~~~~~~~~~~~ +/// +/// typedef std::auto_ptr int_ptr_t; +/// ^~~~~~~~~~~~~ +/// +/// std::auto_ptr fn(std::auto_ptr); +/// ^~~~~~~~~~~~~ ^~~~~~~~~~~~~ +/// +/// +/// \endcode +TypeLocMatcher makeAutoPtrTypeLocMatcher() { + // Skip elaboratedType() as the named type will match soon thereafter. + return typeLoc(loc(qualType(AutoPtrType, unless(elaboratedType())))) + .bind(AutoPtrTokenId); +} + +/// \brief Creates a matcher that finds the using declarations referring to +/// \c std::auto_ptr. +/// +/// \code +/// using std::auto_ptr; +/// ^~~~~~~~~~~~~~~~~~~ +/// \endcode +DeclarationMatcher makeAutoPtrUsingDeclMatcher() { + return usingDecl(hasAnyUsingShadowDecl(hasTargetDecl( + allOf(hasName("auto_ptr"), isFromStdNamespace())))) + .bind(AutoPtrTokenId); +} + +/// \brief Creates a matcher that finds the \c std::auto_ptr copy-ctor and +/// assign-operator expressions. +/// +/// \c AutoPtrOwnershipTransferId is assigned to the argument of the expression, +/// this is the part that has to be wrapped by \c std::move(). +/// +/// \code +/// std::auto_ptr i, j; +/// i = j; +/// ~~~~^ +/// \endcode +StatementMatcher makeTransferOwnershipExprMatcher() { + return anyOf( + cxxOperatorCallExpr(allOf(hasOverloadedOperatorName("="), + callee(cxxMethodDecl(ofClass(AutoPtrDecl))), + hasArgument(1, MovableArgumentMatcher))), + cxxConstructExpr(allOf(hasType(AutoPtrType), argumentCountIs(1), + hasArgument(0, MovableArgumentMatcher)))); +} + +/// \brief Locates the \c auto_ptr token when it is referred by a \c TypeLoc. +/// +/// \code +/// std::auto_ptr i; +/// ^~~~~~~~~~~~~ +/// \endcode +/// +/// The caret represents the location returned and the tildes cover the +/// parameter \p AutoPtrTypeLoc. +/// +/// \return An invalid \c SourceLocation if not found, otherwise the location +/// of the beginning of the \c auto_ptr token. +static SourceLocation locateFromTypeLoc(const TypeLoc *AutoPtrTypeLoc, + const SourceManager &SM) { + auto TL = AutoPtrTypeLoc->getAs(); + if (TL.isNull()) + return SourceLocation(); + + return TL.getTemplateNameLoc(); +} + +/// \brief Locates the \c auto_ptr token in using declarations. +/// +/// \code +/// using std::auto_ptr; +/// ^ +/// \endcode +/// +/// The caret represents the location returned. +/// +/// \return An invalid \c SourceLocation if not found, otherwise the location +/// of the beginning of the \c auto_ptr token. +static SourceLocation locateFromUsingDecl(const UsingDecl *UsingAutoPtrDecl, + const SourceManager &SM) { + return UsingAutoPtrDecl->getNameInfo().getBeginLoc(); +} + +/// \brief Verifies that the token at \p TokenStart is 'auto_ptr'. +static bool checkTokenIsAutoPtr(SourceLocation TokenStart, + const SourceManager &SM, + const LangOptions &LO) { + SmallVector Buffer; + bool Invalid = false; + StringRef Res = Lexer::getSpelling(TokenStart, Buffer, SM, LO, &Invalid); + + return (!Invalid && Res == "auto_ptr"); +} + +ReplaceAutoPtrCheck::ReplaceAutoPtrCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void ReplaceAutoPtrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void ReplaceAutoPtrCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) { + Finder->addMatcher(makeAutoPtrTypeLocMatcher(), this); + Finder->addMatcher(makeAutoPtrUsingDeclMatcher(), this); + Finder->addMatcher(makeTransferOwnershipExprMatcher(), 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) { + 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(), ")"); + + auto Insertion = + Inserter->CreateIncludeInsertion(SM.getMainFileID(), "utility", + /*IsAngled=*/true); + if (Insertion.hasValue()) + Diag << Insertion.getValue(); + + return; + } + + SourceLocation IdentifierLoc; + if (const auto *TL = Result.Nodes.getNodeAs(AutoPtrTokenId)) { + IdentifierLoc = locateFromTypeLoc(TL, SM); + } else if (const auto *D = + Result.Nodes.getNodeAs(AutoPtrTokenId)) { + IdentifierLoc = locateFromUsingDecl(D, SM); + } else { + llvm_unreachable("Bad Callback. No node provided."); + } + + if (IdentifierLoc.isMacroID()) + IdentifierLoc = SM.getSpellingLoc(IdentifierLoc); + + // Ensure that only the 'auto_ptr' token is replaced and not the template + // aliases. + if (!checkTokenIsAutoPtr(IdentifierLoc, SM, LangOptions())) + return; + + SourceLocation EndLoc = + IdentifierLoc.getLocWithOffset(strlen("auto_ptr") - 1); + diag(IdentifierLoc, "auto_ptr is deprecated, use unique_ptr instead") + << FixItHint::CreateReplacement(SourceRange(IdentifierLoc, 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/ShrinkToFitCheck.cpp b/clang-tidy/modernize/ShrinkToFitCheck.cpp new file mode 100644 index 000000000..ef9201828 --- /dev/null +++ b/clang-tidy/modernize/ShrinkToFitCheck.cpp @@ -0,0 +1,90 @@ +//===--- ShrinkToFitCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ShrinkToFitCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +void ShrinkToFitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + // Swap as a function need not to be considered, because rvalue can not + // be bound to a non-const reference. + const auto ShrinkableAsMember = + memberExpr(member(valueDecl().bind("ContainerDecl"))); + const auto ShrinkableAsDecl = + declRefExpr(hasDeclaration(valueDecl().bind("ContainerDecl"))); + const auto CopyCtorCall = cxxConstructExpr(hasArgument( + 0, anyOf(ShrinkableAsMember, ShrinkableAsDecl, + unaryOperator(has(ignoringParenImpCasts(ShrinkableAsMember))), + unaryOperator(has(ignoringParenImpCasts(ShrinkableAsDecl)))))); + const auto SwapParam = + expr(anyOf(memberExpr(member(equalsBoundNode("ContainerDecl"))), + declRefExpr(hasDeclaration(equalsBoundNode("ContainerDecl"))), + unaryOperator(has(ignoringParenImpCasts( + memberExpr(member(equalsBoundNode("ContainerDecl")))))), + unaryOperator(has(ignoringParenImpCasts(declRefExpr( + hasDeclaration(equalsBoundNode("ContainerDecl")))))))); + + Finder->addMatcher( + cxxMemberCallExpr( + on(hasType(namedDecl( + hasAnyName("std::basic_string", "std::deque", "std::vector")))), + callee(cxxMethodDecl(hasName("swap"))), + has(ignoringParenImpCasts(memberExpr(hasDescendant(CopyCtorCall)))), + hasArgument(0, SwapParam.bind("ContainerToShrink")), + unless(isInTemplateInstantiation())) + .bind("CopyAndSwapTrick"), + this); +} + +void ShrinkToFitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("CopyAndSwapTrick"); + const auto *Container = Result.Nodes.getNodeAs("ContainerToShrink"); + FixItHint Hint; + + if (!MemberCall->getLocStart().isMacroID()) { + const LangOptions &Opts = getLangOpts(); + std::string ReplacementText; + if (const auto *UnaryOp = llvm::dyn_cast(Container)) { + ReplacementText = + Lexer::getSourceText(CharSourceRange::getTokenRange( + UnaryOp->getSubExpr()->getSourceRange()), + *Result.SourceManager, Opts); + ReplacementText += "->shrink_to_fit()"; + } else { + ReplacementText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Container->getSourceRange()), + *Result.SourceManager, Opts); + ReplacementText += ".shrink_to_fit()"; + } + + Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), + ReplacementText); + } + + diag(MemberCall->getLocStart(), "the shrink_to_fit method should be used " + "to reduce the capacity of a shrinkable " + "container") + << Hint; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/ShrinkToFitCheck.h b/clang-tidy/modernize/ShrinkToFitCheck.h new file mode 100644 index 000000000..1e3745cd1 --- /dev/null +++ b/clang-tidy/modernize/ShrinkToFitCheck.h @@ -0,0 +1,37 @@ +//===--- ShrinkToFitCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Replace copy and swap tricks on shrinkable containers with the +/// `shrink_to_fit()` method call. +/// +/// The `shrink_to_fit()` method is more readable and more effective than +/// the copy and swap trick to reduce the capacity of a shrinkable container. +/// Note that, the `shrink_to_fit()` method is only available in C++11 and up. +class ShrinkToFitCheck : public ClangTidyCheck { +public: + ShrinkToFitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_SHRINKTOFITCHECK_H diff --git a/clang-tidy/modernize/UseAutoCheck.cpp b/clang-tidy/modernize/UseAutoCheck.cpp new file mode 100644 index 000000000..12c19f6d2 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.cpp @@ -0,0 +1,463 @@ +//===--- UseAutoCheck.cpp - clang-tidy-------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseAutoCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace modernize { +namespace { + +const char IteratorDeclStmtId[] = "iterator_decl"; +const char DeclWithNewId[] = "decl_new"; +const char DeclWithCastId[] = "decl_cast"; +const char DeclWithTemplateCastId[] = "decl_template"; + +/// \brief Matches variable declarations that have explicit initializers that +/// are not initializer lists. +/// +/// Given +/// \code +/// iterator I = Container.begin(); +/// MyType A(42); +/// MyType B{2}; +/// MyType C; +/// \endcode +/// +/// varDecl(hasWrittenNonListInitializer()) maches \c I and \c A but not \c B +/// or \c C. +AST_MATCHER(VarDecl, hasWrittenNonListInitializer) { + const Expr *Init = Node.getAnyInitializer(); + if (!Init) + return false; + + Init = Init->IgnoreImplicit(); + + // The following test is based on DeclPrinter::VisitVarDecl() to find if an + // initializer is implicit or not. + if (const auto *Construct = dyn_cast(Init)) { + return !Construct->isListInitialization() && Construct->getNumArgs() > 0 && + !Construct->getArg(0)->isDefaultArgument(); + } + return Node.getInitStyle() != VarDecl::ListInit; +} + +/// \brief Matches QualTypes that are type sugar for QualTypes that match \c +/// SugarMatcher. +/// +/// Given +/// \code +/// class C {}; +/// typedef C my_type; +/// typedef my_type my_other_type; +/// \endcode +/// +/// qualType(isSugarFor(recordType(hasDeclaration(namedDecl(hasName("C")))))) +/// matches \c my_type and \c my_other_type. +AST_MATCHER_P(QualType, isSugarFor, Matcher, SugarMatcher) { + QualType QT = Node; + while (true) { + if (SugarMatcher.matches(QT, Finder, Builder)) + return true; + + QualType NewQT = QT.getSingleStepDesugaredType(Finder->getASTContext()); + if (NewQT == QT) + return false; + QT = NewQT; + } +} + +/// \brief Matches named declarations that have one of the standard iterator +/// names: iterator, reverse_iterator, const_iterator, const_reverse_iterator. +/// +/// Given +/// \code +/// iterator I; +/// const_iterator CI; +/// \endcode +/// +/// namedDecl(hasStdIteratorName()) matches \c I and \c CI. +AST_MATCHER(NamedDecl, hasStdIteratorName) { + static const char *const IteratorNames[] = {"iterator", "reverse_iterator", + "const_iterator", + "const_reverse_iterator"}; + + for (const char *Name : IteratorNames) { + if (hasName(Name).matches(Node, Finder, Builder)) + return true; + } + return false; +} + +/// \brief Matches named declarations that have one of the standard container +/// names. +/// +/// Given +/// \code +/// class vector {}; +/// class forward_list {}; +/// class my_ver{}; +/// \endcode +/// +/// recordDecl(hasStdContainerName()) matches \c vector and \c forward_list +/// but not \c my_vec. +AST_MATCHER(NamedDecl, hasStdContainerName) { + static const char *const ContainerNames[] = { + "array", "deque", + "forward_list", "list", + "vector", + + "map", "multimap", + "set", "multiset", + + "unordered_map", "unordered_multimap", + "unordered_set", "unordered_multiset", + + "queue", "priority_queue", + "stack"}; + + for (const char *Name : ContainerNames) { + if (hasName(Name).matches(Node, Finder, Builder)) + return true; + } + return false; +} + +/// Matches declarations whose declaration context is the C++ standard library +/// namespace std. +/// +/// Note that inline namespaces are silently ignored during the lookup since +/// both libstdc++ and libc++ are known to use them for versioning purposes. +/// +/// Given: +/// \code +/// namespace ns { +/// struct my_type {}; +/// using namespace std; +/// } +/// +/// using std::vector; +/// using ns:my_type; +/// using ns::list; +/// \code +/// +/// usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(isFromStdNamespace()))) +/// matches "using std::vector" and "using ns::list". +AST_MATCHER(Decl, isFromStdNamespace) { + const DeclContext *D = Node.getDeclContext(); + + while (D->isInlineNamespace()) + D = D->getParent(); + + if (!D->isNamespace() || !D->getParent()->isTranslationUnit()) + return false; + + const IdentifierInfo *Info = cast(D)->getIdentifier(); + + return (Info && Info->isStr("std")); +} + +/// Matches declaration reference or member expressions with explicit template +/// arguments. +AST_POLYMORPHIC_MATCHER(hasExplicitTemplateArgs, + AST_POLYMORPHIC_SUPPORTED_TYPES(DeclRefExpr, + MemberExpr)) { + return Node.hasExplicitTemplateArgs(); +} + +/// \brief Returns a DeclarationMatcher that matches standard iterators nested +/// inside records with a standard container name. +DeclarationMatcher standardIterator() { + return allOf( + namedDecl(hasStdIteratorName()), + hasDeclContext(recordDecl(hasStdContainerName(), isFromStdNamespace()))); +} + +/// \brief Returns a TypeMatcher that matches typedefs for standard iterators +/// inside records with a standard container name. +TypeMatcher typedefIterator() { + return typedefType(hasDeclaration(standardIterator())); +} + +/// \brief Returns a TypeMatcher that matches records named for standard +/// iterators nested inside records named for standard containers. +TypeMatcher nestedIterator() { + return recordType(hasDeclaration(standardIterator())); +} + +/// \brief Returns a TypeMatcher that matches types declared with using +/// declarations and which name standard iterators for standard containers. +TypeMatcher iteratorFromUsingDeclaration() { + auto HasIteratorDecl = hasDeclaration(namedDecl(hasStdIteratorName())); + // Types resulting from using declarations are represented by elaboratedType. + return elaboratedType(allOf( + // Unwrap the nested name specifier to test for one of the standard + // containers. + hasQualifier(specifiesType(templateSpecializationType(hasDeclaration( + namedDecl(hasStdContainerName(), isFromStdNamespace()))))), + // the named type is what comes after the final '::' in the type. It + // should name one of the standard iterator names. + namesType( + anyOf(typedefType(HasIteratorDecl), recordType(HasIteratorDecl))))); +} + +/// \brief This matcher returns declaration statements that contain variable +/// declarations with written non-list initializer for standard iterators. +StatementMatcher makeIteratorDeclMatcher() { + return declStmt(unless(has( + varDecl(anyOf(unless(hasWrittenNonListInitializer()), + unless(hasType(isSugarFor(anyOf( + typedefIterator(), nestedIterator(), + iteratorFromUsingDeclaration()))))))))) + .bind(IteratorDeclStmtId); +} + +StatementMatcher makeDeclWithNewMatcher() { + return declStmt( + unless(has(varDecl(anyOf( + unless(hasInitializer(ignoringParenImpCasts(cxxNewExpr()))), + // FIXME: TypeLoc information is not reliable where CV + // qualifiers are concerned so these types can't be + // handled for now. + hasType(pointerType( + pointee(hasCanonicalType(hasLocalQualifiers())))), + + // FIXME: Handle function pointers. For now we ignore them + // because the replacement replaces the entire type + // specifier source range which includes the identifier. + hasType(pointsTo( + pointsTo(parenType(innerType(functionType())))))))))) + .bind(DeclWithNewId); +} + +StatementMatcher makeDeclWithCastMatcher() { + return declStmt( + unless(has(varDecl(unless(hasInitializer(explicitCastExpr())))))) + .bind(DeclWithCastId); +} + +StatementMatcher makeDeclWithTemplateCastMatcher() { + auto ST = + substTemplateTypeParmType(hasReplacementType(equalsBoundNode("arg"))); + + auto ExplicitCall = + anyOf(has(memberExpr(hasExplicitTemplateArgs())), + has(ignoringImpCasts(declRefExpr(hasExplicitTemplateArgs())))); + + auto TemplateArg = + hasTemplateArgument(0, refersToType(qualType().bind("arg"))); + + auto TemplateCall = callExpr( + ExplicitCall, + callee(functionDecl(TemplateArg, + returns(anyOf(ST, pointsTo(ST), references(ST)))))); + + return declStmt(unless(has(varDecl( + unless(hasInitializer(ignoringImplicit(TemplateCall))))))) + .bind(DeclWithTemplateCastId); +} + +StatementMatcher makeCombinedMatcher() { + return declStmt( + // At least one varDecl should be a child of the declStmt to ensure + // it's a declaration list and avoid matching other declarations, + // e.g. using directives. + has(varDecl(unless(isImplicit()))), + // Skip declarations that are already using auto. + unless(has(varDecl(anyOf(hasType(autoType()), + hasType(qualType(hasDescendant(autoType()))))))), + anyOf(makeIteratorDeclMatcher(), makeDeclWithNewMatcher(), + makeDeclWithCastMatcher(), makeDeclWithTemplateCastMatcher())); +} + +} // namespace + +UseAutoCheck::UseAutoCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RemoveStars(Options.get("RemoveStars", 0)) {} + +void UseAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "RemoveStars", RemoveStars ? 1 : 0); +} + +void UseAutoCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) { + Finder->addMatcher(makeCombinedMatcher(), this); + } +} + +void UseAutoCheck::replaceIterators(const DeclStmt *D, ASTContext *Context) { + for (const auto *Dec : D->decls()) { + const auto *V = cast(Dec); + const Expr *ExprInit = V->getInit(); + + // Skip expressions with cleanups from the intializer expression. + if (const auto *E = dyn_cast(ExprInit)) + ExprInit = E->getSubExpr(); + + const auto *Construct = dyn_cast(ExprInit); + if (!Construct) + continue; + + // Ensure that the constructor receives a single argument. + if (Construct->getNumArgs() != 1) + return; + + // Drill down to the as-written initializer. + const Expr *E = (*Construct->arg_begin())->IgnoreParenImpCasts(); + if (E != E->IgnoreConversionOperator()) { + // We hit a conversion operator. Early-out now as they imply an implicit + // conversion from a different type. Could also mean an explicit + // conversion from the same type but that's pretty rare. + return; + } + + if (const auto *NestedConstruct = dyn_cast(E)) { + // If we ran into an implicit conversion contructor, can't convert. + // + // FIXME: The following only checks if the constructor can be used + // implicitly, not if it actually was. Cases where the converting + // constructor was used explicitly won't get converted. + if (NestedConstruct->getConstructor()->isConvertingConstructor(false)) + return; + } + if (!Context->hasSameType(V->getType(), E->getType())) + return; + } + + // Get the type location using the first declaration. + const auto *V = cast(*D->decl_begin()); + + // WARNING: TypeLoc::getSourceRange() will include the identifier for things + // like function pointers. Not a concern since this action only works with + // iterators but something to keep in mind in the future. + + SourceRange Range(V->getTypeSourceInfo()->getTypeLoc().getSourceRange()); + diag(Range.getBegin(), "use auto when declaring iterators") + << FixItHint::CreateReplacement(Range, "auto"); +} + +void UseAutoCheck::replaceExpr(const DeclStmt *D, ASTContext *Context, + std::function GetType, + StringRef Message) { + const auto *FirstDecl = dyn_cast(*D->decl_begin()); + // Ensure that there is at least one VarDecl within the DeclStmt. + if (!FirstDecl) + return; + + const QualType FirstDeclType = FirstDecl->getType().getCanonicalType(); + + std::vector StarRemovals; + for (const auto *Dec : D->decls()) { + const auto *V = cast(Dec); + // Ensure that every DeclStmt child is a VarDecl. + if (!V) + return; + + const auto *Expr = V->getInit()->IgnoreParenImpCasts(); + // Ensure that every VarDecl has an initializer. + if (!Expr) + return; + + // If VarDecl and Initializer have mismatching unqualified types. + if (!Context->hasSameUnqualifiedType(V->getType(), GetType(Expr))) + return; + + // All subsequent variables in this declaration should have the same + // canonical type. For example, we don't want to use `auto` in + // `T *p = new T, **pp = new T*;`. + if (FirstDeclType != V->getType().getCanonicalType()) + return; + + if (RemoveStars) { + // Remove explicitly written '*' from declarations where there's more than + // one declaration in the declaration list. + if (Dec == *D->decl_begin()) + continue; + + auto Q = V->getTypeSourceInfo()->getTypeLoc().getAs(); + while (!Q.isNull()) { + StarRemovals.push_back(FixItHint::CreateRemoval(Q.getStarLoc())); + Q = Q.getNextTypeLoc().getAs(); + } + } + } + + // FIXME: There is, however, one case we can address: when the VarDecl pointee + // is the same as the initializer, just more CV-qualified. However, TypeLoc + // information is not reliable where CV qualifiers are concerned so we can't + // do anything about this case for now. + TypeLoc Loc = FirstDecl->getTypeSourceInfo()->getTypeLoc(); + if (!RemoveStars) { + while (Loc.getTypeLocClass() == TypeLoc::Pointer || + Loc.getTypeLocClass() == TypeLoc::Qualified) + Loc = Loc.getNextTypeLoc(); + } + while (Loc.getTypeLocClass() == TypeLoc::LValueReference || + Loc.getTypeLocClass() == TypeLoc::RValueReference || + Loc.getTypeLocClass() == TypeLoc::Qualified) { + Loc = Loc.getNextTypeLoc(); + } + SourceRange Range(Loc.getSourceRange()); + auto Diag = diag(Range.getBegin(), Message); + + // Space after 'auto' to handle cases where the '*' in the pointer type is + // next to the identifier. This avoids changing 'int *p' into 'autop'. + // FIXME: This doesn't work for function pointers because the variable name + // is inside the type. + Diag << FixItHint::CreateReplacement(Range, RemoveStars ? "auto " : "auto") + << StarRemovals; +} + +void UseAutoCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Decl = Result.Nodes.getNodeAs(IteratorDeclStmtId)) { + replaceIterators(Decl, Result.Context); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithNewId)) { + replaceExpr(Decl, Result.Context, + [](const Expr *Expr) { return Expr->getType(); }, + "use auto when initializing with new to avoid " + "duplicating the type name"); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithCastId)) { + replaceExpr( + Decl, Result.Context, + [](const Expr *Expr) { + return cast(Expr)->getTypeAsWritten(); + }, + "use auto when initializing with a cast to avoid duplicating the type " + "name"); + } else if (const auto *Decl = + Result.Nodes.getNodeAs(DeclWithTemplateCastId)) { + replaceExpr( + Decl, Result.Context, + [](const Expr *Expr) { + return cast(Expr->IgnoreImplicit()) + ->getDirectCallee() + ->getReturnType(); + }, + "use auto when initializing with a template cast to avoid duplicating " + "the type name"); + } else { + llvm_unreachable("Bad Callback. No node provided."); + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseAutoCheck.h b/clang-tidy/modernize/UseAutoCheck.h new file mode 100644 index 000000000..882e757a1 --- /dev/null +++ b/clang-tidy/modernize/UseAutoCheck.h @@ -0,0 +1,39 @@ +//===--- UseAutoCheck.h - clang-tidy-----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class UseAutoCheck : public ClangTidyCheck { +public: + UseAutoCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void replaceIterators(const DeclStmt *D, ASTContext *Context); + void replaceExpr(const DeclStmt *D, ASTContext *Context, + std::function GetType, + StringRef Message); + + const bool RemoveStars; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_AUTO_H diff --git a/clang-tidy/modernize/UseBoolLiteralsCheck.cpp b/clang-tidy/modernize/UseBoolLiteralsCheck.cpp new file mode 100644 index 000000000..193bb61f5 --- /dev/null +++ b/clang-tidy/modernize/UseBoolLiteralsCheck.cpp @@ -0,0 +1,66 @@ +//===--- 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 { + +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; + + auto Diag = + diag(Expression->getExprLoc(), + "converting integer literal to bool, use bool literal instead"); + + if (!Expression->getLocStart().isMacroID()) + 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..46d787995 --- /dev/null +++ b/clang-tidy/modernize/UseBoolLiteralsCheck.h @@ -0,0 +1,35 @@ +//===--- 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) + : 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_BOOL_LITERALS_H diff --git a/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp new file mode 100644 index 000000000..d0d4c657e --- /dev/null +++ b/clang-tidy/modernize/UseDefaultMemberInitCheck.cpp @@ -0,0 +1,238 @@ +//===--- 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) {} + +void UseDefaultMemberInitCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "UseAssignment", UseAssignment); +} + +void UseDefaultMemberInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus11) + return; + + auto Init = + anyOf(stringLiteral(), characterLiteral(), integerLiteral(), + unaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("-")), + hasUnaryOperand(integerLiteral())), + floatLiteral(), + unaryOperator(anyOf(hasOperatorName("+"), hasOperatorName("-")), + hasUnaryOperand(floatLiteral())), + cxxBoolLiteral(), cxxNullPtrLiteralExpr(), implicitValueInitExpr(), + declRefExpr(to(enumConstantDecl()))); + + Finder->addMatcher( + cxxConstructorDecl( + isDefaultConstructor(), unless(isInstantiated()), + forEachConstructorInitializer( + allOf(forField(unless(anyOf(isBitField(), + hasInClassInitializer(anything())))), + cxxCtorInitializer(isWritten(), + withInitializer(ignoringImplicit(Init))) + .bind("default")))), + this); + + Finder->addMatcher( + cxxConstructorDecl( + unless(ast_matchers::isTemplateInstantiation()), + forEachConstructorInitializer( + allOf(forField(hasInClassInitializer(anything())), + cxxCtorInitializer(isWritten(), + withInitializer(ignoringImplicit(Init))) + .bind("existing")))), + this); +} + +void UseDefaultMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Default = + Result.Nodes.getNodeAs("default")) + checkDefaultInit(Result, Default); + else if (const auto *Existing = + Result.Nodes.getNodeAs("existing")) + checkExistingInit(Result, Existing); + else + llvm_unreachable("Bad Callback. No node provided."); +} + +void UseDefaultMemberInitCheck::checkDefaultInit( + const MatchFinder::MatchResult &Result, const CXXCtorInitializer *Init) { + const FieldDecl *Field = Init->getMember(); + + SourceLocation 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..6d3b19b68 --- /dev/null +++ b/clang-tidy/modernize/UseDefaultMemberInitCheck.h @@ -0,0 +1,45 @@ +//===--- 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; +}; + +} // 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..214354f9f --- /dev/null +++ b/clang-tidy/modernize/UseEmplaceCheck.cpp @@ -0,0 +1,143 @@ +//===--- 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 { + +static const auto DefaultContainersWithPushBack = + "::std::vector; ::std::list; ::std::deque"; +static const auto DefaultSmartPointers = + "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr"; + +namespace { +namespace impl { +// FIXME: This matcher should be replaced by a matcher from ASTMatcher.h +const ast_matchers::internal::VariadicDynCastAllOfMatcher cxxStdInitializerListExpr; +} // namespace impl +} // namespace + +UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ContainersWithPushBack(utils::options::parseStringList(Options.get( + "ContainersWithPushBack", DefaultContainersWithPushBack))), + SmartPointers(utils::options::parseStringList( + Options.get("SmartPointers", DefaultSmartPointers))) {} + +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 + // + match for make_pair calls. + 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(impl::cxxStdInitializerListExpr())); + // FIXME: Replace internal C++ initializer list matcher with one from + // ASTMatchers.h + + // 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 ctorAsArgument = materializeTemporaryExpr( + anyOf(hasConstructExpr, has(cxxFunctionalCastExpr(hasConstructExpr)))); + + Finder->addMatcher(cxxMemberCallExpr(callPushBack, has(ctorAsArgument), + unless(isInTemplateInstantiation())) + .bind("call"), + this); +} + +void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *InnerCtorCall = Result.Nodes.getNodeAs("ctor"); + + 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; + + Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, + "emplace_back("); + + auto CallParensRange = InnerCtorCall->getParenOrBraceRange(); + + // Finish if there is no explicit constructor call. + if (CallParensRange.getBegin().isInvalid()) + return; + + // Range for constructor name and opening brace. + auto CtorCallSourceRange = CharSourceRange::getTokenRange( + InnerCtorCall->getExprLoc(), CallParensRange.getBegin()); + + Diag << FixItHint::CreateRemoval(CtorCallSourceRange) + << 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)); +} + +} // 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..72e1cfd32 --- /dev/null +++ b/clang-tidy/modernize/UseEmplaceCheck.h @@ -0,0 +1,44 @@ +//===--- UseEmplaceCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EMPLACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EMPLACE_H + +#include "../ClangTidy.h" +#include +#include + +namespace clang { +namespace tidy { +namespace modernize { + +/// This check looks for cases when inserting new element into std::vector but +/// the element is constructed temporarily. +/// It replaces those calls for emplace_back of arguments passed to +/// constructor of temporary object. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-emplace.html +class UseEmplaceCheck : public ClangTidyCheck { +public: + UseEmplaceCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + std::vector ContainersWithPushBack; + std::vector SmartPointers; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EMPLACE_H diff --git a/clang-tidy/modernize/UseEqualsDefaultCheck.cpp b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp new file mode 100644 index 000000000..1b677147b --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDefaultCheck.cpp @@ -0,0 +1,299 @@ +//===--- UseEqualsDefaultCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseEqualsDefaultCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +static const char SpecialFunction[] = "SpecialFunction"; + +/// \brief Finds all the named non-static fields of \p Record. +static std::set +getAllNamedFields(const CXXRecordDecl *Record) { + std::set Result; + for (const auto *Field : Record->fields()) { + // Static data members are not in this range. + if (Field->isUnnamedBitfield()) + continue; + Result.insert(Field); + } + return Result; +} + +/// \brief Returns the names of the direct bases of \p Record, both virtual and +/// non-virtual. +static std::set getAllDirectBases(const CXXRecordDecl *Record) { + std::set Result; + for (auto Base : Record->bases()) { + // CXXBaseSpecifier. + const auto *BaseType = Base.getTypeSourceInfo()->getType().getTypePtr(); + Result.insert(BaseType); + } + return Result; +} + +/// \brief Returns a matcher that matches member expressions where the base is +/// the variable declared as \p Var and the accessed member is the one declared +/// as \p Field. +internal::Matcher accessToFieldInVar(const FieldDecl *Field, + const ValueDecl *Var) { + return ignoringImpCasts( + memberExpr(hasObjectExpression(declRefExpr(to(varDecl(equalsNode(Var))))), + member(fieldDecl(equalsNode(Field))))); +} + +/// \brief Check that the given constructor has copy signature and that it +/// copy-initializes all its bases and members. +static bool isCopyConstructorAndCanBeDefaulted(ASTContext *Context, + const CXXConstructorDecl *Ctor) { + // An explicitly-defaulted constructor cannot have default arguments. + if (Ctor->getMinRequiredArguments() != 1) + return false; + + const auto *Record = Ctor->getParent(); + const auto *Param = Ctor->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // The initialization of a base class should be a call to a copy + // constructor of the base. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isBaseInitializer(), + withInitializer(cxxConstructExpr(allOf( + hasType(equalsNode(Base)), + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument( + 0, declRefExpr(to(varDecl(equalsNode(Param))))))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + auto AccessToFieldInParam = accessToFieldInVar(Field, Param); + // The initialization is a CXXConstructExpr for class types. + if (match( + cxxConstructorDecl(forEachConstructorInitializer(cxxCtorInitializer( + isMemberInitializer(), forField(equalsNode(Field)), + withInitializer(anyOf( + AccessToFieldInParam, + cxxConstructExpr(allOf( + hasDeclaration(cxxConstructorDecl(isCopyConstructor())), + argumentCountIs(1), + hasArgument(0, AccessToFieldInParam)))))))), + *Ctor, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else, like initializing an indirect base. + return Ctor->getNumCtorInitializers() == + BasesToInit.size() + FieldsToInit.size(); +} + +/// \brief Checks that the given method is an overloading of the assignment +/// operator, has copy signature, returns a reference to "*this" and copies +/// all its members and subobjects. +static bool isCopyAssignmentAndCanBeDefaulted(ASTContext *Context, + const CXXMethodDecl *Operator) { + const auto *Record = Operator->getParent(); + const auto *Param = Operator->getParamDecl(0); + + // Base classes and members that have to be copied. + auto BasesToInit = getAllDirectBases(Record); + auto FieldsToInit = getAllNamedFields(Record); + + const auto *Compound = cast(Operator->getBody()); + + // The assignment operator definition has to end with the following return + // statement: + // return *this; + if (Compound->body_empty() || + match(returnStmt(has(ignoringParenImpCasts(unaryOperator( + hasOperatorName("*"), hasUnaryOperand(cxxThisExpr()))))), + *Compound->body_back(), *Context) + .empty()) + return false; + + // Ensure that all the bases are copied. + for (const auto *Base : BasesToInit) { + // Assignment operator of a base class: + // Base::operator=(Other); + // + // Clang translates this into: + // ((Base*)this)->operator=((Base)Other); + // + // So we are looking for a member call that fulfills: + if (match(compoundStmt(has(ignoringParenImpCasts(cxxMemberCallExpr(allOf( + // - The object is an implicit cast of 'this' to a pointer to + // a base class. + onImplicitObjectArgument( + implicitCastExpr(hasImplicitDestinationType( + pointsTo(type(equalsNode(Base)))), + hasSourceExpression(cxxThisExpr()))), + // - The called method is the operator=. + callee(cxxMethodDecl(isCopyAssignmentOperator())), + // - The argument is (an implicit cast to a Base of) the + // argument taken by "Operator". + argumentCountIs(1), + hasArgument(0, + declRefExpr(to(varDecl(equalsNode(Param)))))))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that all the members are copied. + for (const auto *Field : FieldsToInit) { + // The assignment of data members: + // Field = Other.Field; + // Is a BinaryOperator in non-class types, and a CXXOperatorCallExpr + // otherwise. + auto LHS = memberExpr(hasObjectExpression(cxxThisExpr()), + member(fieldDecl(equalsNode(Field)))); + auto RHS = accessToFieldInVar(Field, Param); + if (match( + compoundStmt(has(ignoringParenImpCasts(stmt(anyOf( + binaryOperator(hasOperatorName("="), hasLHS(LHS), hasRHS(RHS)), + cxxOperatorCallExpr(hasOverloadedOperatorName("="), + argumentCountIs(2), hasArgument(0, LHS), + hasArgument(1, RHS))))))), + *Compound, *Context) + .empty()) + return false; + } + + // Ensure that we don't do anything else. + return Compound->size() == BasesToInit.size() + FieldsToInit.size() + 1; +} + +/// \brief Returns false if the body has any non-whitespace character. +static bool bodyEmpty(const ASTContext *Context, const CompoundStmt *Body) { + bool Invalid = false; + StringRef Text = Lexer::getSourceText( + CharSourceRange::getCharRange(Body->getLBracLoc().getLocWithOffset(1), + Body->getRBracLoc()), + Context->getSourceManager(), Context->getLangOpts(), &Invalid); + return !Invalid && std::strspn(Text.data(), " \t\r\n") == Text.size(); +} + +void UseEqualsDefaultCheck::registerMatchers(MatchFinder *Finder) { + if (getLangOpts().CPlusPlus) { + // Destructor. + Finder->addMatcher(cxxDestructorDecl(isDefinition()).bind(SpecialFunction), + this); + Finder->addMatcher( + cxxConstructorDecl( + isDefinition(), + anyOf( + // Default constructor. + allOf(unless(hasAnyConstructorInitializer(isWritten())), + parameterCountIs(0)), + // Copy constructor. + allOf(isCopyConstructor(), + // Discard constructors that can be used as a copy + // constructor because all the other arguments have + // default values. + parameterCountIs(1)))) + .bind(SpecialFunction), + this); + // Copy-assignment operator. + Finder->addMatcher( + cxxMethodDecl(isDefinition(), isCopyAssignmentOperator(), + // isCopyAssignmentOperator() allows the parameter to be + // passed by value, and in this case it cannot be + // defaulted. + hasParameter(0, hasType(lValueReferenceType()))) + .bind(SpecialFunction), + this); + } +} + +void UseEqualsDefaultCheck::check(const MatchFinder::MatchResult &Result) { + std::string SpecialFunctionName; + + // Both CXXConstructorDecl and CXXDestructorDecl inherit from CXXMethodDecl. + const auto *SpecialFunctionDecl = + Result.Nodes.getNodeAs(SpecialFunction); + + // Discard explicitly deleted/defaulted special member functions and those + // that are not user-provided (automatically generated). + if (SpecialFunctionDecl->isDeleted() || + SpecialFunctionDecl->isExplicitlyDefaulted() || + SpecialFunctionDecl->isLateTemplateParsed() || + SpecialFunctionDecl->isTemplateInstantiation() || + !SpecialFunctionDecl->isUserProvided() || !SpecialFunctionDecl->hasBody()) + return; + + const auto *Body = dyn_cast(SpecialFunctionDecl->getBody()); + if (!Body) + return; + + // If there is code inside the body, don't warn. + if (!SpecialFunctionDecl->isCopyAssignmentOperator() && !Body->body_empty()) + return; + + // If there are comments inside the body, don't do the change. + bool ApplyFix = SpecialFunctionDecl->isCopyAssignmentOperator() || + bodyEmpty(Result.Context, Body); + + std::vector RemoveInitializers; + + if (const auto *Ctor = dyn_cast(SpecialFunctionDecl)) { + if (Ctor->getNumParams() == 0) { + SpecialFunctionName = "default constructor"; + } else { + if (!isCopyConstructorAndCanBeDefaulted(Result.Context, Ctor)) + return; + SpecialFunctionName = "copy constructor"; + // If there are constructor initializers, they must be removed. + for (const auto *Init : Ctor->inits()) { + RemoveInitializers.emplace_back( + FixItHint::CreateRemoval(Init->getSourceRange())); + } + } + } else if (isa(SpecialFunctionDecl)) { + SpecialFunctionName = "destructor"; + } else { + if (!isCopyAssignmentAndCanBeDefaulted(Result.Context, SpecialFunctionDecl)) + return; + SpecialFunctionName = "copy-assignment operator"; + } + + // The location of the body is more useful inside a macro as spelling and + // expansion locations are reported. + SourceLocation Location = SpecialFunctionDecl->getLocation(); + if (Location.isMacroID()) + Location = Body->getLocStart(); + + auto Diag = diag(Location, "use '= default' to define a trivial " + + SpecialFunctionName); + + if (ApplyFix) + Diag << FixItHint::CreateReplacement(Body->getSourceRange(), "= default;") + << RemoveInitializers; +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseEqualsDefaultCheck.h b/clang-tidy/modernize/UseEqualsDefaultCheck.h new file mode 100644 index 000000000..dfefed6dc --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDefaultCheck.h @@ -0,0 +1,50 @@ +//===--- UseEqualsDefaultCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DEFAULT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DEFAULT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// \brief Replace default bodies of special member functions with '= default;'. +/// \code +/// struct A { +/// A() {} +/// ~A(); +/// }; +/// A::~A() {} +/// \endcode +/// Is converted to: +/// \code +/// struct A { +/// A() = default; +/// ~A(); +/// }; +/// A::~A() = default; +/// \endcode +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-equals-default.html +class UseEqualsDefaultCheck : public ClangTidyCheck { +public: + UseEqualsDefaultCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_EQUALS_DEFAULT_H diff --git a/clang-tidy/modernize/UseEqualsDeleteCheck.cpp b/clang-tidy/modernize/UseEqualsDeleteCheck.cpp new file mode 100644 index 000000000..3c890faaf --- /dev/null +++ b/clang-tidy/modernize/UseEqualsDeleteCheck.cpp @@ -0,0 +1,73 @@ +//===--- 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 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)) { + // FIXME: Add FixItHint to make 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/UseNullptrCheck.cpp b/clang-tidy/modernize/UseNullptrCheck.cpp new file mode 100644 index 000000000..50c2f8906 --- /dev/null +++ b/clang-tidy/modernize/UseNullptrCheck.cpp @@ -0,0 +1,495 @@ +//===--- 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(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()); + if (!C) { + FirstSubExpr = nullptr; + return true; + } + + if (!FirstSubExpr) + FirstSubExpr = C->getSubExpr()->IgnoreParens(); + + // Ignore the expr if it is already a nullptr literal expr. + if (isa(FirstSubExpr)) + return true; + + if (C->getCastKind() != CK_NullToPointer && + C->getCastKind() != CK_NullToMemberPointer) { + return true; + } + + SourceLocation StartLoc = FirstSubExpr->getLocStart(); + SourceLocation EndLoc = FirstSubExpr->getLocEnd(); + + // If the location comes from a macro arg expansion, *all* uses of that + // arg must be checked to result in NullTo(Member)Pointer casts. + // + // If the location comes from a macro body expansion, check to see if its + // coming from one of the allowed 'NULL' macros. + if (SM.isMacroArgExpansion(StartLoc) && SM.isMacroArgExpansion(EndLoc)) { + SourceLocation FileLocStart = SM.getFileLoc(StartLoc), + FileLocEnd = SM.getFileLoc(EndLoc); + SourceLocation ImmediateMarcoArgLoc, MacroLoc; + // Skip NULL macros used in macro. + if (!getMacroAndArgLocations(StartLoc, ImmediateMarcoArgLoc, MacroLoc) || + ImmediateMarcoArgLoc != FileLocStart) + return skipSubTree(); + + if (isReplaceableRange(FileLocStart, FileLocEnd, SM) && + allArgUsesValid(C)) { + replaceWithNullptr(Check, SM, FileLocStart, FileLocEnd); + } + return skipSubTree(); + } + + if (SM.isMacroBodyExpansion(StartLoc) && SM.isMacroBodyExpansion(EndLoc)) { + StringRef OutermostMacroName = + getOutermostMacroName(StartLoc, SM, Context.getLangOpts()); + + // Check to see if the user wants to replace the macro being expanded. + if (std::find(NullMacros.begin(), NullMacros.end(), OutermostMacroName) == + NullMacros.end()) { + return skipSubTree(); + } + + StartLoc = SM.getFileLoc(StartLoc); + EndLoc = SM.getFileLoc(EndLoc); + } + + if (!isReplaceableRange(StartLoc, EndLoc, SM)) { + return skipSubTree(); + } + replaceWithNullptr(Check, SM, StartLoc, EndLoc); + + return true; + } + +private: + bool skipSubTree() { + PruneSubtree = true; + return true; + } + + /// \brief Tests that all expansions of a macro arg, one of which expands to + /// result in \p CE, yield NullTo(Member)Pointer casts. + bool allArgUsesValid(const CastExpr *CE) { + SourceLocation CastLoc = CE->getLocStart(); + + // Step 1: Get location of macro arg and location of the macro the arg was + // provided to. + SourceLocation ArgLoc, MacroLoc; + if (!getMacroAndArgLocations(CastLoc, ArgLoc, MacroLoc)) + return false; + + // Step 2: Find the first ancestor that doesn't expand from this macro. + ast_type_traits::DynTypedNode ContainingAncestor; + if (!findContainingAncestor( + ast_type_traits::DynTypedNode::create(*CE), MacroLoc, + ContainingAncestor)) + return false; + + // Step 3: + // Visit children of this containing parent looking for the least-descended + // nodes of the containing parent which are macro arg expansions that expand + // from the given arg location. + // Visitor needs: arg loc. + MacroArgUsageVisitor ArgUsageVisitor(SM.getFileLoc(CastLoc), SM); + if (const auto *D = ContainingAncestor.get()) + ArgUsageVisitor.TraverseDecl(const_cast(D)); + else if (const auto *S = ContainingAncestor.get()) + ArgUsageVisitor.TraverseStmt(const_cast(S)); + else + llvm_unreachable("Unhandled ContainingAncestor node type"); + + return !ArgUsageVisitor.foundInvalid(); + } + + /// \brief Given the SourceLocation for a macro arg expansion, finds the + /// non-macro SourceLocation of the macro the arg was passed to and the + /// non-macro SourceLocation of the argument in the arg list to that macro. + /// These results are returned via \c MacroLoc and \c ArgLoc respectively. + /// These values are undefined if the return value is false. + /// + /// \returns false if one of the returned SourceLocations would be a + /// SourceLocation pointing within the definition of another macro. + bool getMacroAndArgLocations(SourceLocation Loc, SourceLocation &ArgLoc, + SourceLocation &MacroLoc) { + assert(Loc.isMacroID() && "Only reasonble to call this on macros"); + + ArgLoc = Loc; + + // Find the location of the immediate macro expansion. + while (true) { + std::pair LocInfo = SM.getDecomposedLoc(ArgLoc); + const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first); + const SrcMgr::ExpansionInfo &Expansion = E->getExpansion(); + + SourceLocation OldArgLoc = ArgLoc; + ArgLoc = Expansion.getExpansionLocStart(); + if (!Expansion.isMacroArgExpansion()) { + if (!MacroLoc.isFileID()) + return false; + + StringRef Name = + Lexer::getImmediateMacroName(OldArgLoc, SM, Context.getLangOpts()); + return std::find(NullMacros.begin(), NullMacros.end(), Name) != + NullMacros.end(); + } + + MacroLoc = SM.getExpansionRange(ArgLoc).first; + + ArgLoc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second); + if (ArgLoc.isFileID()) + return true; + + // If spelling location resides in the same FileID as macro expansion + // location, it means there is no inner macro. + FileID MacroFID = SM.getFileID(MacroLoc); + if (SM.isInFileID(ArgLoc, MacroFID)) { + // Don't transform this case. If the characters that caused the + // null-conversion come from within a macro, they can't be changed. + return false; + } + } + + llvm_unreachable("getMacroAndArgLocations"); + } + + /// \brief Tests if TestMacroLoc is found while recursively unravelling + /// expansions starting at TestLoc. TestMacroLoc.isFileID() must be true. + /// Implementation is very similar to getMacroAndArgLocations() except in this + /// case, it's not assumed that TestLoc is expanded from a macro argument. + /// While unravelling expansions macro arguments are handled as with + /// getMacroAndArgLocations() but in this function macro body expansions are + /// also handled. + /// + /// False means either: + /// - TestLoc is not from a macro expansion. + /// - TestLoc is from a different macro expansion. + bool expandsFrom(SourceLocation TestLoc, SourceLocation TestMacroLoc) { + if (TestLoc.isFileID()) { + return false; + } + + SourceLocation Loc = TestLoc, MacroLoc; + + while (true) { + std::pair LocInfo = SM.getDecomposedLoc(Loc); + const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first); + const SrcMgr::ExpansionInfo &Expansion = E->getExpansion(); + + Loc = Expansion.getExpansionLocStart(); + + if (!Expansion.isMacroArgExpansion()) { + if (Loc.isFileID()) { + return Loc == TestMacroLoc; + } + // Since Loc is still a macro ID and it's not an argument expansion, we + // don't need to do the work of handling an argument expansion. Simply + // keep recursively expanding until we hit a FileID or a macro arg + // expansion or a macro arg expansion. + continue; + } + + MacroLoc = SM.getImmediateExpansionRange(Loc).first; + if (MacroLoc.isFileID() && MacroLoc == TestMacroLoc) { + // Match made. + return true; + } + + Loc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second); + if (Loc.isFileID()) { + // If we made it this far without finding a match, there is no match to + // be made. + return false; + } + } + + llvm_unreachable("expandsFrom"); + } + + /// \brief Given a starting point \c Start in the AST, find an ancestor that + /// doesn't expand from the macro called at file location \c MacroLoc. + /// + /// \pre MacroLoc.isFileID() + /// \returns true if such an ancestor was found, false otherwise. + bool findContainingAncestor(ast_type_traits::DynTypedNode Start, + SourceLocation MacroLoc, + ast_type_traits::DynTypedNode &Result) { + // Below we're only following the first parent back up the AST. This should + // be fine since for the statements we care about there should only be one + // parent, except for the case specified below. + + assert(MacroLoc.isFileID()); + + while (true) { + const auto &Parents = Context.getParents(Start); + if (Parents.empty()) + return false; + if (Parents.size() > 1) { + // If there are more than one parents, don't do the replacement unless + // they are InitListsExpr (semantic and syntactic form). In this case we + // can choose any one here, and the ASTVisitor will take care of + // traversing the right one. + for (const auto &Parent : Parents) { + if (!Parent.get()) + return false; + } + } + + const ast_type_traits::DynTypedNode &Parent = Parents[0]; + + SourceLocation Loc; + if (const auto *D = Parent.get()) + Loc = D->getLocStart(); + else if (const auto *S = Parent.get()) + Loc = S->getLocStart(); + + // TypeLoc and NestedNameSpecifierLoc are members of the parent map. Skip + // them and keep going up. + if (Loc.isValid()) { + if (!expandsFrom(Loc, MacroLoc)) { + Result = Parent; + return true; + } + } + Start = Parent; + } + + llvm_unreachable("findContainingAncestor"); + } + +private: + SourceManager &SM; + ASTContext &Context; + ArrayRef NullMacros; + ClangTidyCheck &Check; + Expr *FirstSubExpr; + bool PruneSubtree; +}; + +} // namespace + +UseNullptrCheck::UseNullptrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NullMacrosStr(Options.get("NullMacros", "")) { + StringRef(NullMacrosStr).split(NullMacros, ","); +} + +void UseNullptrCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "NullMacros", NullMacrosStr); +} + +void UseNullptrCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matcher for C++. Because this checker is used for + // modernization, it is reasonable to run it on any C++ standard with the + // assumption the user is trying to modernize their codebase. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(makeCastSequenceMatcher(), this); +} + +void UseNullptrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *NullCast = Result.Nodes.getNodeAs(CastSequence); + assert(NullCast && "Bad Callback. No node provided"); + + // Given an implicit null-ptr cast or an explicit cast with an implicit + // null-to-pointer cast within use CastSequenceVisitor to identify sequences + // of explicit casts that can be converted into 'nullptr'. + CastSequenceVisitor(*Result.Context, NullMacros, *this) + .TraverseStmt(const_cast(NullCast)); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseNullptrCheck.h b/clang-tidy/modernize/UseNullptrCheck.h new file mode 100644 index 000000000..4b33f1ee0 --- /dev/null +++ b/clang-tidy/modernize/UseNullptrCheck.h @@ -0,0 +1,35 @@ +//===--- UseNullptrCheck.h - clang-tidy--------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +class UseNullptrCheck : public ClangTidyCheck { +public: + UseNullptrCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string NullMacrosStr; + SmallVector NullMacros; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_NULLPTR_H diff --git a/clang-tidy/modernize/UseOverrideCheck.cpp b/clang-tidy/modernize/UseOverrideCheck.cpp new file mode 100644 index 000000000..be01d2158 --- /dev/null +++ b/clang-tidy/modernize/UseOverrideCheck.cpp @@ -0,0 +1,203 @@ +//===--- 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; + while (!RawLexer.LexFromRawLexer(Tok)) { + if (Tok.is(tok::semi) || Tok.is(tok::l_brace)) + break; + 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()); + } + 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. + Token LastNonCommentToken; + for (Token T : Tokens) { + if (!T.is(tok::comment)) { + LastNonCommentToken = T; + } + } + InsertLoc = LastNonCommentToken.getEndLoc(); + ReplacementText = " override"; + } + + if (!InsertLoc.isValid()) { + // For declarations marked with "= 0" or "= [default|delete]", the end + // location will point until after those markings. Therefore, the override + // keyword shouldn't be inserted at the end, but before the '='. + if (Tokens.size() > 2 && (GetText(Tokens.back(), Sources) == "0" || + Tokens.back().is(tok::kw_default) || + Tokens.back().is(tok::kw_delete)) && + GetText(Tokens[Tokens.size() - 2], Sources) == "=") { + InsertLoc = Tokens[Tokens.size() - 2].getLocation(); + // Check if we need to insert a space. + if ((Tokens[Tokens.size() - 2].getFlags() & Token::LeadingSpace) == 0) + ReplacementText = " override "; + } else if (GetText(Tokens.back(), Sources) == "ABSTRACT") { + InsertLoc = Tokens.back().getLocation(); + } + } + + if (!InsertLoc.isValid()) { + InsertLoc = FileRange.getEnd(); + ReplacementText = " override"; + } + Diag << FixItHint::CreateInsertion(InsertLoc, ReplacementText); + } + + if (HasFinal && HasOverride) { + SourceLocation OverrideLoc = Method->getAttr()->getLocation(); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(OverrideLoc, OverrideLoc)); + } + + if (HasVirtual) { + for (Token Tok : Tokens) { + if (Tok.is(tok::kw_virtual)) { + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + Tok.getLocation(), Tok.getLocation())); + break; + } + } + } +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseOverrideCheck.h b/clang-tidy/modernize/UseOverrideCheck.h new file mode 100644 index 000000000..83ce7da79 --- /dev/null +++ b/clang-tidy/modernize/UseOverrideCheck.h @@ -0,0 +1,32 @@ +//===--- UseOverrideCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Use C++11's `override` and remove `virtual` where applicable. +class UseOverrideCheck : public ClangTidyCheck { +public: + UseOverrideCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEOVERRIDECHECK_H diff --git a/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp b/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp new file mode 100644 index 000000000..7f5920076 --- /dev/null +++ b/clang-tidy/modernize/UseTransparentFunctorsCheck.cpp @@ -0,0 +1,131 @@ +//===--- UseTransparentFunctorsCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UseTransparentFunctorsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +UseTransparentFunctorsCheck::UseTransparentFunctorsCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), SafeMode(Options.get("SafeMode", 0)) {} + +void UseTransparentFunctorsCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "SafeMode", SafeMode ? 1 : 0); +} + +void UseTransparentFunctorsCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus14) + return; + + const auto TransparentFunctors = + classTemplateSpecializationDecl( + unless(hasAnyTemplateArgument(refersToType(voidType()))), + hasAnyName("::std::plus", "::std::minus", "::std::multiplies", + "::std::divides", "::std::modulus", "::std::negate", + "::std::equal_to", "::std::not_equal_to", "::std::greater", + "::std::less", "::std::greater_equal", "::std::less_equal", + "::std::logical_and", "::std::logical_or", + "::std::logical_not", "::std::bit_and", "::std::bit_or", + "::std::bit_xor", "::std::bit_not")) + .bind("FunctorClass"); + + // Non-transparent functor mentioned as a template parameter. FIXIT. + Finder->addMatcher( + loc(qualType( + unless(elaboratedType()), + hasDeclaration(classTemplateSpecializationDecl( + unless(hasAnyTemplateArgument(templateArgument(refersToType( + qualType(pointsTo(qualType(isAnyCharacter()))))))), + hasAnyTemplateArgument( + templateArgument(refersToType(qualType(hasDeclaration( + TransparentFunctors)))) + .bind("Functor")))))) + .bind("FunctorParentLoc"), + this); + + if (SafeMode) + return; + + // Non-transparent functor constructed. No FIXIT. There is no easy way + // to rule out the problematic char* vs string case. + Finder->addMatcher(cxxConstructExpr(hasDeclaration(cxxMethodDecl( + ofClass(TransparentFunctors))), + unless(isInTemplateInstantiation())) + .bind("FuncInst"), + this); +} + +static const StringRef Message = "prefer transparent functors '%0'"; + +template static T getInnerTypeLocAs(TypeLoc Loc) { + T Result; + while (Result.isNull() && !Loc.isNull()) { + Result = Loc.getAs(); + Loc = Loc.getNextTypeLoc(); + } + return Result; +} + +void UseTransparentFunctorsCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *FuncClass = + Result.Nodes.getNodeAs("FunctorClass"); + if (const auto *FuncInst = + Result.Nodes.getNodeAs("FuncInst")) { + diag(FuncInst->getLocStart(), Message) + << (FuncClass->getName() + "<>").str(); + return; + } + + const auto *Functor = Result.Nodes.getNodeAs("Functor"); + const auto FunctorParentLoc = + Result.Nodes.getNodeAs("FunctorParentLoc") + ->getAs(); + + if (!FunctorParentLoc) + return; + + unsigned ArgNum = 0; + const auto *FunctorParentType = + FunctorParentLoc.getType()->castAs(); + for (; ArgNum < FunctorParentType->getNumArgs(); ++ArgNum) { + const TemplateArgument &Arg = FunctorParentType->getArg(ArgNum); + if (Arg.getKind() != TemplateArgument::Type) + continue; + QualType ParentArgType = Arg.getAsType(); + if (ParentArgType->isRecordType() && + ParentArgType->getAsCXXRecordDecl() == + Functor->getAsType()->getAsCXXRecordDecl()) + break; + } + // Functor is a default template argument. + if (ArgNum == FunctorParentType->getNumArgs()) + return; + TemplateArgumentLoc FunctorLoc = FunctorParentLoc.getArgLoc(ArgNum); + auto FunctorTypeLoc = getInnerTypeLocAs( + FunctorLoc.getTypeSourceInfo()->getTypeLoc()); + if (FunctorTypeLoc.isNull()) + return; + + SourceLocation ReportLoc = FunctorLoc.getLocation(); + diag(ReportLoc, Message) << (FuncClass->getName() + "<>").str() + << FixItHint::CreateRemoval( + FunctorTypeLoc.getArgLoc(0).getSourceRange()); +} + +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/modernize/UseTransparentFunctorsCheck.h b/clang-tidy/modernize/UseTransparentFunctorsCheck.h new file mode 100644 index 000000000..4bdce766e --- /dev/null +++ b/clang-tidy/modernize/UseTransparentFunctorsCheck.h @@ -0,0 +1,37 @@ +//===--- UseTransparentFunctorsCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_TRANSPARENT_FUNCTORS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_TRANSPARENT_FUNCTORS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Prefer using transparent functors to non-transparent ones. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-transparent-functors.html +class UseTransparentFunctorsCheck : public ClangTidyCheck { +public: + UseTransparentFunctorsCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; +private: + const bool SafeMode; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USE_TRANSPARENT_FUNCTORS_H diff --git a/clang-tidy/modernize/UseUsingCheck.cpp b/clang-tidy/modernize/UseUsingCheck.cpp new file mode 100644 index 000000000..fdeda99a8 --- /dev/null +++ b/clang-tidy/modernize/UseUsingCheck.cpp @@ -0,0 +1,94 @@ +//===--- 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 { + +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, const SourceLocation &LocStart, + const SourceLocation &LocEnd, ASTContext &Context, + SourceRange &ResultRange) { + FileID FID = SM.getFileID(LocEnd); + llvm::MemoryBuffer *Buffer = SM.getBuffer(FID, LocEnd); + Lexer DeclLexer(SM.getLocForStartOfFile(FID), Context.getLangOpts(), + Buffer->getBufferStart(), SM.getCharacterData(LocStart), + Buffer->getBufferEnd()); + Token DeclToken; + bool result = false; + int parenthesisLevel = 0; + + while (!DeclLexer.LexFromRawLexer(DeclToken)) { + if (DeclToken.getKind() == tok::TokenKind::l_paren) + parenthesisLevel++; + if (DeclToken.getKind() == tok::TokenKind::r_paren) + parenthesisLevel--; + if (DeclToken.getKind() == tok::TokenKind::semi) + break; + // if there is comma and we are not between open parenthesis then it is + // two or more declatarions in this chain + if (parenthesisLevel == 0 && DeclToken.getKind() == tok::TokenKind::comma) + return false; + + if (DeclToken.isOneOf(tok::TokenKind::identifier, + tok::TokenKind::raw_identifier)) { + auto TokenStr = DeclToken.getRawIdentifier().str(); + + if (TokenStr == "typedef") { + ResultRange = + SourceRange(DeclToken.getLocation(), DeclToken.getEndLoc()); + result = true; + } + } + } + // assert if there was keyword 'typedef' in declaration + assert(result && "No typedef found"); + + return result; +} + +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; + + auto Diag = + diag(MatchedDecl->getLocStart(), "use 'using' instead of 'typedef'"); + if (MatchedDecl->getLocStart().isMacroID()) { + return; + } + SourceRange RemovalRange; + if (CheckRemoval(SM, MatchedDecl->getLocStart(), MatchedDecl->getLocEnd(), + Context, RemovalRange)) { + Diag << FixItHint::CreateReplacement( + MatchedDecl->getSourceRange(), + "using " + MatchedDecl->getNameAsString() + " = " + + MatchedDecl->getUnderlyingType().getAsString(getLangOpts())); + } +} + +} // 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..f1c70de0a --- /dev/null +++ b/clang-tidy/modernize/UseUsingCheck.h @@ -0,0 +1,35 @@ +//===--- 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 { +public: + UseUsingCheck(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_USING_H diff --git a/clang-tidy/mpi/BufferDerefCheck.cpp b/clang-tidy/mpi/BufferDerefCheck.cpp new file mode 100644 index 000000000..8e2c0e34e --- /dev/null +++ b/clang-tidy/mpi/BufferDerefCheck.cpp @@ -0,0 +1,132 @@ +//===--- BufferDerefCheck.cpp - clang-tidy---------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BufferDerefCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Checkers/MPIFunctionClassifier.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace mpi { + +void BufferDerefCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("CE"), this); +} + +void BufferDerefCheck::check(const MatchFinder::MatchResult &Result) { + static ento::mpi::MPIFunctionClassifier FuncClassifier(*Result.Context); + const auto *CE = Result.Nodes.getNodeAs("CE"); + if (!CE->getDirectCallee()) + return; + + const IdentifierInfo *Identifier = CE->getDirectCallee()->getIdentifier(); + if (!Identifier || !FuncClassifier.isMPIType(Identifier)) + return; + + // These containers are used, to capture the type and expression of a buffer. + SmallVector BufferTypes; + SmallVector BufferExprs; + + // Adds the type and expression of a buffer that is used in the MPI call + // expression to the captured containers. + auto addBuffer = [&CE, &Result, &BufferTypes, + &BufferExprs](const size_t BufferIdx) { + // Skip null pointer constants and in place 'operators'. + if (CE->getArg(BufferIdx)->isNullPointerConstant( + *Result.Context, Expr::NPC_ValueDependentIsNull) || + tooling::fixit::getText(*CE->getArg(BufferIdx), *Result.Context) == + "MPI_IN_PLACE") + return; + + const Expr *ArgExpr = CE->getArg(BufferIdx); + if (!ArgExpr) + return; + const Type *ArgType = ArgExpr->IgnoreImpCasts()->getType().getTypePtr(); + if (!ArgType) + return; + BufferExprs.push_back(ArgExpr); + BufferTypes.push_back(ArgType); + }; + + // Collect buffer types and argument expressions for all buffers used in the + // MPI call expression. The number passed to the lambda corresponds to the + // argument index of the currently verified MPI function call. + if (FuncClassifier.isPointToPointType(Identifier)) { + addBuffer(0); + } else if (FuncClassifier.isCollectiveType(Identifier)) { + if (FuncClassifier.isReduceType(Identifier)) { + addBuffer(0); + addBuffer(1); + } else if (FuncClassifier.isScatterType(Identifier) || + FuncClassifier.isGatherType(Identifier) || + FuncClassifier.isAlltoallType(Identifier)) { + addBuffer(0); + addBuffer(3); + } else if (FuncClassifier.isBcastType(Identifier)) { + addBuffer(0); + } + } + + checkBuffers(BufferTypes, BufferExprs); +} + +void BufferDerefCheck::checkBuffers(ArrayRef BufferTypes, + ArrayRef BufferExprs) { + for (size_t i = 0; i < BufferTypes.size(); ++i) { + unsigned IndirectionCount = 0; + const Type *BufferType = BufferTypes[i]; + llvm::SmallVector Indirections; + + // Capture the depth and types of indirections for the passed buffer. + while (true) { + if (BufferType->isPointerType()) { + BufferType = BufferType->getPointeeType().getTypePtr(); + Indirections.push_back(IndirectionType::Pointer); + } else if (BufferType->isArrayType()) { + BufferType = BufferType->getArrayElementTypeNoTypeQual(); + Indirections.push_back(IndirectionType::Array); + } else { + break; + } + ++IndirectionCount; + } + + if (IndirectionCount > 1) { + // Referencing an array with '&' is valid, as this also points to the + // beginning of the array. + if (IndirectionCount == 2 && + Indirections[0] == IndirectionType::Pointer && + Indirections[1] == IndirectionType::Array) + return; + + // Build the indirection description in reverse order of discovery. + std::string IndirectionDesc; + for (auto It = Indirections.rbegin(); It != Indirections.rend(); ++It) { + if (!IndirectionDesc.empty()) + IndirectionDesc += "->"; + if (*It == IndirectionType::Pointer) { + IndirectionDesc += "pointer"; + } else { + IndirectionDesc += "array"; + } + } + + const auto Loc = BufferExprs[i]->getSourceRange().getBegin(); + diag(Loc, "buffer is insufficiently dereferenced: %0") << IndirectionDesc; + } + } +} + +} // namespace mpi +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/mpi/BufferDerefCheck.h b/clang-tidy/mpi/BufferDerefCheck.h new file mode 100644 index 000000000..8490fa1c8 --- /dev/null +++ b/clang-tidy/mpi/BufferDerefCheck.h @@ -0,0 +1,51 @@ +//===--- BufferDerefCheck.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_BUFFER_DEREF_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_BUFFER_DEREF_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace mpi { + +/// This check verifies if a buffer passed to an MPI (Message Passing Interface) +/// function is sufficiently dereferenced. Buffers should be passed as a single +/// pointer or array. As MPI function signatures specify void * for their buffer +/// types, insufficiently dereferenced buffers can be passed, like for example +/// as double pointers or multidimensional arrays, without a compiler warning +/// emitted. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/mpi-buffer-deref.html +class BufferDerefCheck : public ClangTidyCheck { +public: + BufferDerefCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Checks for all buffers in an MPI call if they are sufficiently + /// dereferenced. + /// + /// \param BufferTypes buffer types + /// \param BufferExprs buffer arguments as expressions + void checkBuffers(ArrayRef BufferTypes, + ArrayRef BufferExprs); + + enum class IndirectionType : unsigned char { Pointer, Array }; +}; + +} // namespace mpi +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_BUFFER_DEREF_H diff --git a/clang-tidy/mpi/CMakeLists.txt b/clang-tidy/mpi/CMakeLists.txt new file mode 100644 index 000000000..36584342c --- /dev/null +++ b/clang-tidy/mpi/CMakeLists.txt @@ -0,0 +1,17 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyMPIModule + BufferDerefCheck.cpp + MPITidyModule.cpp + TypeMismatchCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + clangStaticAnalyzerCheckers + ) diff --git a/clang-tidy/mpi/MPITidyModule.cpp b/clang-tidy/mpi/MPITidyModule.cpp new file mode 100644 index 000000000..55e187dbe --- /dev/null +++ b/clang-tidy/mpi/MPITidyModule.cpp @@ -0,0 +1,39 @@ +//===--- MPITidyModule.cpp - clang-tidy -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "BufferDerefCheck.h" +#include "TypeMismatchCheck.h" + +namespace clang { +namespace tidy { +namespace mpi { + +class MPIModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("mpi-buffer-deref"); + CheckFactories.registerCheck("mpi-type-mismatch"); + } +}; + +} // namespace mpi + +// Register the MPITidyModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("mpi-module", "Adds MPI clang-tidy checks."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the MPIModule. +volatile int MPIModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/mpi/TypeMismatchCheck.cpp b/clang-tidy/mpi/TypeMismatchCheck.cpp new file mode 100644 index 000000000..a1f92b8f7 --- /dev/null +++ b/clang-tidy/mpi/TypeMismatchCheck.cpp @@ -0,0 +1,335 @@ +//===--- TypeMismatchCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypeMismatchCheck.h" +#include "clang/Lex/Lexer.h" +#include "clang/StaticAnalyzer/Checkers/MPIFunctionClassifier.h" +#include "clang/Tooling/FixIt.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace mpi { + +/// Check if a BuiltinType::Kind matches the MPI datatype. +/// +/// \param MultiMap datatype group +/// \param Kind buffer type kind +/// \param MPIDatatype name of the MPI datatype +/// +/// \returns true if the pair matches +static bool +isMPITypeMatching(const std::multimap &MultiMap, + const BuiltinType::Kind Kind, + const std::string &MPIDatatype) { + auto ItPair = MultiMap.equal_range(Kind); + while (ItPair.first != ItPair.second) { + if (ItPair.first->second == MPIDatatype) + return true; + ++ItPair.first; + } + return false; +} + +/// Check if the MPI datatype is a standard type. +/// +/// \param MPIDatatype name of the MPI datatype +/// +/// \returns true if the type is a standard type +static bool isStandardMPIDatatype(const std::string &MPIDatatype) { + static std::unordered_set AllTypes = { + "MPI_C_BOOL", + "MPI_CHAR", + "MPI_SIGNED_CHAR", + "MPI_UNSIGNED_CHAR", + "MPI_WCHAR", + "MPI_INT", + "MPI_LONG", + "MPI_SHORT", + "MPI_LONG_LONG", + "MPI_LONG_LONG_INT", + "MPI_UNSIGNED", + "MPI_UNSIGNED_SHORT", + "MPI_UNSIGNED_LONG", + "MPI_UNSIGNED_LONG_LONG", + "MPI_FLOAT", + "MPI_DOUBLE", + "MPI_LONG_DOUBLE", + "MPI_C_COMPLEX", + "MPI_C_FLOAT_COMPLEX", + "MPI_C_DOUBLE_COMPLEX", + "MPI_C_LONG_DOUBLE_COMPLEX", + "MPI_INT8_T", + "MPI_INT16_T", + "MPI_INT32_T", + "MPI_INT64_T", + "MPI_UINT8_T", + "MPI_UINT16_T", + "MPI_UINT32_T", + "MPI_UINT64_T", + "MPI_CXX_BOOL", + "MPI_CXX_FLOAT_COMPLEX", + "MPI_CXX_DOUBLE_COMPLEX", + "MPI_CXX_LONG_DOUBLE_COMPLEX"}; + + return AllTypes.find(MPIDatatype) != AllTypes.end(); +} + +/// Check if a BuiltinType matches the MPI datatype. +/// +/// \param Builtin the builtin type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// \param LO language options +/// +/// \returns true if the type matches +static bool isBuiltinTypeMatching(const BuiltinType *Builtin, + std::string &BufferTypeName, + const std::string &MPIDatatype, + const LangOptions &LO) { + static std::multimap BuiltinMatches = { + // On some systems like PPC or ARM, 'char' is unsigned by default which is + // why distinct signedness for the buffer and MPI type is tolerated. + {BuiltinType::SChar, "MPI_CHAR"}, + {BuiltinType::SChar, "MPI_SIGNED_CHAR"}, + {BuiltinType::SChar, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::Char_S, "MPI_CHAR"}, + {BuiltinType::Char_S, "MPI_SIGNED_CHAR"}, + {BuiltinType::Char_S, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::UChar, "MPI_CHAR"}, + {BuiltinType::UChar, "MPI_SIGNED_CHAR"}, + {BuiltinType::UChar, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::Char_U, "MPI_CHAR"}, + {BuiltinType::Char_U, "MPI_SIGNED_CHAR"}, + {BuiltinType::Char_U, "MPI_UNSIGNED_CHAR"}, + {BuiltinType::WChar_S, "MPI_WCHAR"}, + {BuiltinType::WChar_U, "MPI_WCHAR"}, + {BuiltinType::Bool, "MPI_C_BOOL"}, + {BuiltinType::Bool, "MPI_CXX_BOOL"}, + {BuiltinType::Short, "MPI_SHORT"}, + {BuiltinType::Int, "MPI_INT"}, + {BuiltinType::Long, "MPI_LONG"}, + {BuiltinType::LongLong, "MPI_LONG_LONG"}, + {BuiltinType::LongLong, "MPI_LONG_LONG_INT"}, + {BuiltinType::UShort, "MPI_UNSIGNED_SHORT"}, + {BuiltinType::UInt, "MPI_UNSIGNED"}, + {BuiltinType::ULong, "MPI_UNSIGNED_LONG"}, + {BuiltinType::ULongLong, "MPI_UNSIGNED_LONG_LONG"}, + {BuiltinType::Float, "MPI_FLOAT"}, + {BuiltinType::Double, "MPI_DOUBLE"}, + {BuiltinType::LongDouble, "MPI_LONG_DOUBLE"}}; + + if (!isMPITypeMatching(BuiltinMatches, Builtin->getKind(), MPIDatatype)) { + BufferTypeName = Builtin->getName(LO); + return false; + } + + return true; +} + +/// Check if a complex float/double/long double buffer type matches +/// the MPI datatype. +/// +/// \param Complex buffer type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// \param LO language options +/// +/// \returns true if the type matches or the buffer type is unknown +static bool isCComplexTypeMatching(const ComplexType *const Complex, + std::string &BufferTypeName, + const std::string &MPIDatatype, + const LangOptions &LO) { + static std::multimap ComplexCMatches = { + {BuiltinType::Float, "MPI_C_COMPLEX"}, + {BuiltinType::Float, "MPI_C_FLOAT_COMPLEX"}, + {BuiltinType::Double, "MPI_C_DOUBLE_COMPLEX"}, + {BuiltinType::LongDouble, "MPI_C_LONG_DOUBLE_COMPLEX"}}; + + const auto *Builtin = + Complex->getElementType().getTypePtr()->getAs(); + + if (Builtin && + !isMPITypeMatching(ComplexCMatches, Builtin->getKind(), MPIDatatype)) { + BufferTypeName = (llvm::Twine(Builtin->getName(LO)) + " _Complex").str(); + return false; + } + return true; +} + +/// Check if a complex templated buffer type matches +/// the MPI datatype. +/// +/// \param Template buffer type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// \param LO language options +/// +/// \returns true if the type matches or the buffer type is unknown +static bool +isCXXComplexTypeMatching(const TemplateSpecializationType *const Template, + std::string &BufferTypeName, + const std::string &MPIDatatype, + const LangOptions &LO) { + static std::multimap ComplexCXXMatches = { + {BuiltinType::Float, "MPI_CXX_FLOAT_COMPLEX"}, + {BuiltinType::Double, "MPI_CXX_DOUBLE_COMPLEX"}, + {BuiltinType::LongDouble, "MPI_CXX_LONG_DOUBLE_COMPLEX"}}; + + if (Template->getAsCXXRecordDecl()->getName() != "complex") + return true; + + const auto *Builtin = + Template->getArg(0).getAsType().getTypePtr()->getAs(); + + if (Builtin && + !isMPITypeMatching(ComplexCXXMatches, Builtin->getKind(), MPIDatatype)) { + BufferTypeName = + (llvm::Twine("complex<") + Builtin->getName(LO) + ">").str(); + return false; + } + + return true; +} + +/// Check if a fixed size width buffer type matches the MPI datatype. +/// +/// \param Typedef buffer type +/// \param BufferTypeName buffer type name, gets assigned +/// \param MPIDatatype name of the MPI datatype +/// +/// \returns true if the type matches or the buffer type is unknown +static bool isTypedefTypeMatching(const TypedefType *const Typedef, + std::string &BufferTypeName, + const std::string &MPIDatatype) { + static llvm::StringMap FixedWidthMatches = { + {"int8_t", "MPI_INT8_T"}, {"int16_t", "MPI_INT16_T"}, + {"int32_t", "MPI_INT32_T"}, {"int64_t", "MPI_INT64_T"}, + {"uint8_t", "MPI_UINT8_T"}, {"uint16_t", "MPI_UINT16_T"}, + {"uint32_t", "MPI_UINT32_T"}, {"uint64_t", "MPI_UINT64_T"}}; + + const auto it = FixedWidthMatches.find(Typedef->getDecl()->getName()); + // Check if the typedef is known and not matching the MPI datatype. + if (it != FixedWidthMatches.end() && it->getValue() != MPIDatatype) { + BufferTypeName = Typedef->getDecl()->getName(); + return false; + } + return true; +} + +/// Get the unqualified, dereferenced type of an argument. +/// +/// \param CE call expression +/// \param idx argument index +/// +/// \returns type of the argument +static const Type *argumentType(const CallExpr *const CE, const size_t idx) { + const QualType QT = CE->getArg(idx)->IgnoreImpCasts()->getType(); + return QT.getTypePtr()->getPointeeOrArrayElementType(); +} + +void TypeMismatchCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr().bind("CE"), this); +} + +void TypeMismatchCheck::check(const MatchFinder::MatchResult &Result) { + static ento::mpi::MPIFunctionClassifier FuncClassifier(*Result.Context); + const auto *const CE = Result.Nodes.getNodeAs("CE"); + if (!CE->getDirectCallee()) + return; + + const IdentifierInfo *Identifier = CE->getDirectCallee()->getIdentifier(); + if (!Identifier || !FuncClassifier.isMPIType(Identifier)) + return; + + // These containers are used, to capture buffer, MPI datatype pairs. + SmallVector BufferTypes; + SmallVector BufferExprs; + SmallVector MPIDatatypes; + + // Adds a buffer, MPI datatype pair of an MPI call expression to the + // containers. For buffers, the type and expression is captured. + auto addPair = [&CE, &Result, &BufferTypes, &BufferExprs, &MPIDatatypes]( + const size_t BufferIdx, const size_t DatatypeIdx) { + // Skip null pointer constants and in place 'operators'. + if (CE->getArg(BufferIdx)->isNullPointerConstant( + *Result.Context, Expr::NPC_ValueDependentIsNull) || + tooling::fixit::getText(*CE->getArg(BufferIdx), *Result.Context) == + "MPI_IN_PLACE") + return; + + StringRef MPIDatatype = + tooling::fixit::getText(*CE->getArg(DatatypeIdx), *Result.Context); + + const Type *ArgType = argumentType(CE, BufferIdx); + // Skip unknown MPI datatypes and void pointers. + if (!isStandardMPIDatatype(MPIDatatype) || ArgType->isVoidType()) + return; + + BufferTypes.push_back(ArgType); + BufferExprs.push_back(CE->getArg(BufferIdx)); + MPIDatatypes.push_back(MPIDatatype); + }; + + // Collect all buffer, MPI datatype pairs for the inspected call expression. + if (FuncClassifier.isPointToPointType(Identifier)) { + addPair(0, 2); + } else if (FuncClassifier.isCollectiveType(Identifier)) { + if (FuncClassifier.isReduceType(Identifier)) { + addPair(0, 3); + addPair(1, 3); + } else if (FuncClassifier.isScatterType(Identifier) || + FuncClassifier.isGatherType(Identifier) || + FuncClassifier.isAlltoallType(Identifier)) { + addPair(0, 2); + addPair(3, 5); + } else if (FuncClassifier.isBcastType(Identifier)) { + addPair(0, 2); + } + } + checkArguments(BufferTypes, BufferExprs, MPIDatatypes, getLangOpts()); +} + +void TypeMismatchCheck::checkArguments(ArrayRef BufferTypes, + ArrayRef BufferExprs, + ArrayRef MPIDatatypes, + const LangOptions &LO) { + std::string BufferTypeName; + + for (size_t i = 0; i < MPIDatatypes.size(); ++i) { + const Type *const BT = BufferTypes[i]; + bool Error = false; + + if (const auto *Typedef = BT->getAs()) { + Error = !isTypedefTypeMatching(Typedef, BufferTypeName, MPIDatatypes[i]); + } else if (const auto *Complex = BT->getAs()) { + Error = + !isCComplexTypeMatching(Complex, BufferTypeName, MPIDatatypes[i], LO); + } else if (const auto *Template = BT->getAs()) { + Error = !isCXXComplexTypeMatching(Template, BufferTypeName, + MPIDatatypes[i], LO); + } else if (const auto *Builtin = BT->getAs()) { + Error = + !isBuiltinTypeMatching(Builtin, BufferTypeName, MPIDatatypes[i], LO); + } + + if (Error) { + const auto Loc = BufferExprs[i]->getSourceRange().getBegin(); + diag(Loc, "buffer type '%0' does not match the MPI datatype '%1'") + << BufferTypeName << MPIDatatypes[i]; + } + } +} + +} // namespace mpi +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/mpi/TypeMismatchCheck.h b/clang-tidy/mpi/TypeMismatchCheck.h new file mode 100644 index 000000000..dd56c465d --- /dev/null +++ b/clang-tidy/mpi/TypeMismatchCheck.h @@ -0,0 +1,51 @@ +//===--- TypeMismatchCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_TYPE_MISMATCH_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_TYPE_MISMATCH_H + +#include "../ClangTidy.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace mpi { + +/// This check verifies if buffer type and MPI (Message Passing Interface) +/// datatype pairs match. All MPI datatypes defined by the MPI standard (3.1) +/// are verified by this check. User defined typedefs, custom MPI datatypes and +/// null pointer constants are skipped, in the course of verification. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/mpi-type-mismatch.html +class TypeMismatchCheck : public ClangTidyCheck { +public: + TypeMismatchCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + /// Check if the buffer type MPI datatype pairs match. + /// + /// \param BufferTypes buffer types + /// \param BufferExprs buffer arguments as expressions + /// \param MPIDatatypes MPI datatype + /// \param LO language options + void checkArguments(ArrayRef BufferTypes, + ArrayRef BufferExprs, + ArrayRef MPIDatatypes, const LangOptions &LO); +}; + +} // namespace mpi +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MPI_TYPE_MISMATCH_H diff --git a/clang-tidy/performance/CMakeLists.txt b/clang-tidy/performance/CMakeLists.txt new file mode 100644 index 000000000..8473f2c1c --- /dev/null +++ b/clang-tidy/performance/CMakeLists.txt @@ -0,0 +1,20 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyPerformanceModule + FasterStringFindCheck.cpp + ForRangeCopyCheck.cpp + ImplicitCastInLoopCheck.cpp + InefficientStringConcatenationCheck.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..a8a9f662e --- /dev/null +++ b/clang-tidy/performance/FasterStringFindCheck.cpp @@ -0,0 +1,112 @@ +//===--- 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 = + anyOf(hasName("find"), hasName("rfind"), hasName("find_first_of"), + hasName("find_first_not_of"), hasName("find_last_of"), + hasName("find_last_not_of")); + + llvm::Optional> IsStringClass; + + for (const auto &ClassName : StringLikeClasses) { + const auto HasName = hasName(ClassName); + IsStringClass = IsStringClass ? anyOf(*IsStringClass, HasName) : HasName; + } + + if (IsStringClass) { + Finder->addMatcher( + cxxMemberCallExpr( + callee(functionDecl(StringFindFunctions).bind("func")), + anyOf(argumentCountIs(1), argumentCountIs(2)), + hasArgument(0, SingleChar), + on(expr(hasType(recordDecl(*IsStringClass)), + unless(hasSubstitutedType())))), + this); + } +} + +void FasterStringFindCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Literal = Result.Nodes.getNodeAs("literal"); + const auto *FindFunc = Result.Nodes.getNodeAs("func"); + + auto Replacement = MakeCharacterLiteral(Literal); + if (!Replacement) + return; + + diag(Literal->getLocStart(), "%0 called with a string literal consisting of " + "a single character; consider using the more " + "effective overload accepting a character") + << FindFunc << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(Literal->getLocStart(), + Literal->getLocEnd()), + *Replacement); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/FasterStringFindCheck.h b/clang-tidy/performance/FasterStringFindCheck.h new file mode 100644 index 000000000..ff666f2fe --- /dev/null +++ b/clang-tidy/performance/FasterStringFindCheck.h @@ -0,0 +1,43 @@ +//===--- FasterStringFindCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H + +#include "../ClangTidy.h" + +#include +#include + +namespace clang { +namespace tidy { +namespace performance { + +/// Optimize calls to std::string::find() and friends when the needle passed is +/// a single character string literal. +/// The character literal overload is more efficient. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-faster-string-find.html +class FasterStringFindCheck : public ClangTidyCheck { +public: + FasterStringFindCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::vector StringLikeClasses; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FASTER_STRING_FIND_H diff --git a/clang-tidy/performance/ForRangeCopyCheck.cpp b/clang-tidy/performance/ForRangeCopyCheck.cpp new file mode 100644 index 000000000..0b9dd2308 --- /dev/null +++ b/clang-tidy/performance/ForRangeCopyCheck.cpp @@ -0,0 +1,95 @@ +//===--- ForRangeCopyCheck.cpp - clang-tidy--------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ForRangeCopyCheck.h" +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/TypeTraits.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +ForRangeCopyCheck::ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnAllAutoCopies(Options.get("WarnOnAllAutoCopies", 0)) {} + +void ForRangeCopyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnAllAutoCopies", WarnOnAllAutoCopies); +} + +void ForRangeCopyCheck::registerMatchers(MatchFinder *Finder) { + // Match loop variables that are not references or pointers or are already + // initialized through MaterializeTemporaryExpr which indicates a type + // conversion. + auto LoopVar = varDecl( + hasType(hasCanonicalType(unless(anyOf(referenceType(), pointerType())))), + unless(hasInitializer(expr(hasDescendant(materializeTemporaryExpr()))))); + Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar"))) + .bind("forRange"), + this); +} + +void ForRangeCopyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Var = Result.Nodes.getNodeAs("loopVar"); + // Ignore code in macros since we can't place the fixes correctly. + if (Var->getLocStart().isMacroID()) + return; + if (handleConstValueCopy(*Var, *Result.Context)) + return; + const auto *ForRange = Result.Nodes.getNodeAs("forRange"); + handleCopyIsOnlyConstReferenced(*Var, *ForRange, *Result.Context); +} + +bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar, + ASTContext &Context) { + if (WarnOnAllAutoCopies) { + // For aggressive check just test that loop variable has auto type. + if (!isa(LoopVar.getType())) + return false; + } else if (!LoopVar.getType().isConstQualified()) { + return false; + } + llvm::Optional Expensive = + utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); + if (!Expensive || !*Expensive) + return false; + auto Diagnostic = + diag(LoopVar.getLocation(), + "the loop variable's type is not a reference type; this creates a " + "copy in each iteration; consider making this a reference") + << utils::fixit::changeVarDeclToReference(LoopVar, Context); + if (!LoopVar.getType().isConstQualified()) + Diagnostic << utils::fixit::changeVarDeclToConst(LoopVar); + return true; +} + +bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced( + const VarDecl &LoopVar, const CXXForRangeStmt &ForRange, + ASTContext &Context) { + llvm::Optional Expensive = + utils::type_traits::isExpensiveToCopy(LoopVar.getType(), Context); + if (LoopVar.getType().isConstQualified() || !Expensive || !*Expensive) + return false; + if (!utils::decl_ref_expr::isOnlyUsedAsConst(LoopVar, *ForRange.getBody(), + Context)) + return false; + diag(LoopVar.getLocation(), + "loop variable is copied but only used as const reference; consider " + "making it a const reference") + << utils::fixit::changeVarDeclToConst(LoopVar) + << utils::fixit::changeVarDeclToReference(LoopVar, Context); + return true; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/ForRangeCopyCheck.h b/clang-tidy/performance/ForRangeCopyCheck.h new file mode 100644 index 000000000..f88a126e5 --- /dev/null +++ b/clang-tidy/performance/ForRangeCopyCheck.h @@ -0,0 +1,49 @@ +//===--- ForRangeCopyCheck.h - clang-tidy------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// A check that detects copied loop variables and suggests using const +/// references. +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-for-range-copy.html +class ForRangeCopyCheck : public ClangTidyCheck { +public: + ForRangeCopyCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + // Checks if the loop variable is a const value and expensive to copy. If so + // suggests it be converted to a const reference. + bool handleConstValueCopy(const VarDecl &LoopVar, ASTContext &Context); + + // Checks if the loop variable is a non-const value and whether only + // const methods are invoked on it or whether it is only used as a const + // reference argument. If so it suggests it be made a const reference. + bool handleCopyIsOnlyConstReferenced(const VarDecl &LoopVar, + const CXXForRangeStmt &ForRange, + ASTContext &Context); + + const bool WarnOnAllAutoCopies; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_FORRANGECOPYCHECK_H diff --git a/clang-tidy/performance/ImplicitCastInLoopCheck.cpp b/clang-tidy/performance/ImplicitCastInLoopCheck.cpp new file mode 100644 index 000000000..7bd3f8a30 --- /dev/null +++ b/clang-tidy/performance/ImplicitCastInLoopCheck.cpp @@ -0,0 +1,97 @@ +//===--- ImplicitCastInLoopCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ImplicitCastInLoopCheck.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { +// Checks if the stmt is a ImplicitCastExpr with a CastKind that is not a NoOp. +// The subtelty is that in some cases (user defined conversions), we can +// get to ImplicitCastExpr inside each other, with the outer one a NoOp. In this +// case we skip the first cast expr. +bool IsNonTrivialImplicitCast(const Stmt *ST) { + if (const auto *ICE = dyn_cast(ST)) { + return (ICE->getCastKind() != CK_NoOp) || + IsNonTrivialImplicitCast(ICE->getSubExpr()); + } + return false; +} +} // namespace + +void ImplicitCastInLoopCheck::registerMatchers(MatchFinder *Finder) { + // We look for const ref loop variables that (optionally inside an + // ExprWithCleanup) materialize a temporary, and contain a implicit cast. The + // check on the implicit cast is done in check() because we can't access + // implicit cast subnode via matchers: has() skips casts and materialize! + // We also bind on the call to operator* to get the proper type in the + // diagnostic message. + // Note that when the implicit cast is done through a user defined cast + // operator, the node is a CXXMemberCallExpr, not a CXXOperatorCallExpr, so + // it should not get caught by the cxxOperatorCallExpr() matcher. + Finder->addMatcher( + cxxForRangeStmt(hasLoopVariable( + varDecl(hasType(qualType(references(qualType(isConstQualified())))), + hasInitializer(expr(hasDescendant(cxxOperatorCallExpr().bind( + "operator-call"))) + .bind("init"))) + .bind("faulty-var"))), + this); +} + +void ImplicitCastInLoopCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs("faulty-var"); + const auto *Init = Result.Nodes.getNodeAs("init"); + const auto *OperatorCall = + Result.Nodes.getNodeAs("operator-call"); + + if (const auto *Cleanup = dyn_cast(Init)) + Init = Cleanup->getSubExpr(); + + const auto *Materialized = dyn_cast(Init); + if (!Materialized) + return; + + // We ignore NoOp casts. Those are generated if the * operator on the + // iterator returns a value instead of a reference, and the loop variable + // is a reference. This situation is fine (it probably produces the same + // code at the end). + if (IsNonTrivialImplicitCast(Materialized->getTemporary())) + ReportAndFix(Result.Context, VD, OperatorCall); +} + +void ImplicitCastInLoopCheck::ReportAndFix( + const ASTContext *Context, const VarDecl *VD, + const CXXOperatorCallExpr *OperatorCall) { + // We only match on const ref, so we should print a const ref version of the + // type. + QualType ConstType = OperatorCall->getType().withConst(); + QualType ConstRefType = Context->getLValueReferenceType(ConstType); + const char Message[] = + "the type of the loop variable %0 is different from the one returned " + "by the iterator and generates an implicit cast; you can either " + "change the type to the correct one (%1 but 'const auto&' is always a " + "valid option) or remove the reference to make it explicit that you are " + "creating a new value"; + diag(VD->getLocStart(), Message) << VD << ConstRefType; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/ImplicitCastInLoopCheck.h b/clang-tidy/performance/ImplicitCastInLoopCheck.h new file mode 100644 index 000000000..3a9659d66 --- /dev/null +++ b/clang-tidy/performance/ImplicitCastInLoopCheck.h @@ -0,0 +1,38 @@ +//===--- ImplicitCastInLoopCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CAST_IN_LOOP_CHECK_H_ +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CAST_IN_LOOP_CHECK_H_ + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +// Checks that in a for range loop, if the provided type is a reference, then +// the underlying type is the one returned by the iterator (i.e. that there +// isn't any implicit conversion). +class ImplicitCastInLoopCheck : public ClangTidyCheck { +public: + ImplicitCastInLoopCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void ReportAndFix(const ASTContext *Context, const VarDecl *VD, + const CXXOperatorCallExpr *OperatorCall); +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_IMPLICIT_CAST_IN_LOOP_CHECK_H_ diff --git a/clang-tidy/performance/InefficientStringConcatenationCheck.cpp b/clang-tidy/performance/InefficientStringConcatenationCheck.cpp new file mode 100644 index 000000000..9e03fbc13 --- /dev/null +++ b/clang-tidy/performance/InefficientStringConcatenationCheck.cpp @@ -0,0 +1,86 @@ +//===--- 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.get("StrictMode", 0)) {} + +void InefficientStringConcatenationCheck::registerMatchers( + MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + const auto BasicStringType = + hasType(cxxRecordDecl(hasName("::std::basic_string"))); + + const auto BasicStringPlusOperator = cxxOperatorCallExpr( + hasOverloadedOperatorName("+"), + hasAnyArgument(ignoringImpCasts(declRefExpr(BasicStringType)))); + + const auto PlusOperator = + cxxOperatorCallExpr( + hasOverloadedOperatorName("+"), + hasAnyArgument(ignoringImpCasts(declRefExpr(BasicStringType))), + hasDescendant(BasicStringPlusOperator)) + .bind("plusOperator"); + + const auto AssignOperator = cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + hasArgument(0, declRefExpr(BasicStringType, + hasDeclaration(decl().bind("lhsStrT"))) + .bind("lhsStr")), + hasArgument(1, stmt(hasDescendant(declRefExpr( + hasDeclaration(decl(equalsBoundNode("lhsStrT"))))))), + hasDescendant(BasicStringPlusOperator)); + + if (StrictMode) { + Finder->addMatcher(cxxOperatorCallExpr(anyOf(AssignOperator, PlusOperator)), + this); + } else { + Finder->addMatcher( + cxxOperatorCallExpr(anyOf(AssignOperator, PlusOperator), + hasAncestor(stmt(anyOf(cxxForRangeStmt(), + whileStmt(), forStmt())))), + this); + } +} + +void InefficientStringConcatenationCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *LhsStr = Result.Nodes.getNodeAs("lhsStr"); + const auto *PlusOperator = + Result.Nodes.getNodeAs("plusOperator"); + const auto DiagMsg = + "string concatenation results in allocation of unnecessary temporary " + "strings; consider using 'operator+=' or 'string::append()' instead"; + + if (LhsStr) + diag(LhsStr->getExprLoc(), DiagMsg); + else if (PlusOperator) + diag(PlusOperator->getExprLoc(), DiagMsg); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/InefficientStringConcatenationCheck.h b/clang-tidy/performance/InefficientStringConcatenationCheck.h new file mode 100644 index 000000000..12a154c22 --- /dev/null +++ b/clang-tidy/performance/InefficientStringConcatenationCheck.h @@ -0,0 +1,41 @@ +//===--- InefficientStringConcatenationCheck.h - clang-tidy-----------*- C++ +//-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTSTRINGCONCATENATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTSTRINGCONCATENATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// This check is to warn about the performance overhead arising from +/// concatenating strings, using the operator+, instead of operator+=. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-inefficient-string-concatenation.html +class InefficientStringConcatenationCheck : public ClangTidyCheck { +public: + InefficientStringConcatenationCheck(StringRef Name, + ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const bool StrictMode; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_INEFFICIENTSTRINGCONCATENATION_H diff --git a/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tidy/performance/PerformanceTidyModule.cpp new file mode 100644 index 000000000..90255c5e2 --- /dev/null +++ b/clang-tidy/performance/PerformanceTidyModule.cpp @@ -0,0 +1,56 @@ +//===--- PeformanceTidyModule.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "FasterStringFindCheck.h" +#include "ForRangeCopyCheck.h" +#include "ImplicitCastInLoopCheck.h" +#include "InefficientStringConcatenationCheck.h" +#include "TypePromotionInMathFnCheck.h" +#include "UnnecessaryCopyInitialization.h" +#include "UnnecessaryValueParamCheck.h" + +namespace clang { +namespace tidy { +namespace performance { + +class PerformanceModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "performance-faster-string-find"); + CheckFactories.registerCheck( + "performance-for-range-copy"); + CheckFactories.registerCheck( + "performance-implicit-cast-in-loop"); + CheckFactories.registerCheck( + "performance-inefficient-string-concatenation"); + CheckFactories.registerCheck( + "performance-type-promotion-in-math-fn"); + CheckFactories.registerCheck( + "performance-unnecessary-copy-initialization"); + CheckFactories.registerCheck( + "performance-unnecessary-value-param"); + } +}; + +// Register the PerformanceModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("performance-module", "Adds performance checks."); + +} // namespace performance + +// This anchor is used to force the linker to link in the generated object file +// and thus register the PerformanceModule. +volatile int PerformanceModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/TypePromotionInMathFnCheck.cpp b/clang-tidy/performance/TypePromotionInMathFnCheck.cpp new file mode 100644 index 000000000..82f9535e0 --- /dev/null +++ b/clang-tidy/performance/TypePromotionInMathFnCheck.cpp @@ -0,0 +1,205 @@ +//===--- TypePromotionInMathFnCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypePromotionInMathFnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/StringSet.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { +AST_MATCHER_P(Type, isBuiltinType, BuiltinType::Kind, Kind) { + if (const auto *BT = dyn_cast(&Node)) { + return BT->getKind() == Kind; + } + return false; +} +} // anonymous namespace + +TypePromotionInMathFnCheck::TypePromotionInMathFnCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void TypePromotionInMathFnCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + IncludeInserter = llvm::make_unique( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle); + Compiler.getPreprocessor().addPPCallbacks( + IncludeInserter->CreatePPCallbacks()); +} + +void TypePromotionInMathFnCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void TypePromotionInMathFnCheck::registerMatchers(MatchFinder *Finder) { + constexpr BuiltinType::Kind IntTy = BuiltinType::Int; + constexpr BuiltinType::Kind LongTy = BuiltinType::Long; + constexpr BuiltinType::Kind FloatTy = BuiltinType::Float; + constexpr BuiltinType::Kind DoubleTy = BuiltinType::Double; + constexpr BuiltinType::Kind LongDoubleTy = BuiltinType::LongDouble; + + auto hasBuiltinTyParam = [](int Pos, BuiltinType::Kind Kind) { + return hasParameter(Pos, hasType(isBuiltinType(Kind))); + }; + auto hasBuiltinTyArg = [](int Pos, BuiltinType::Kind Kind) { + return hasArgument(Pos, hasType(isBuiltinType(Kind))); + }; + + // Match calls to foo(double) with a float argument. + auto OneDoubleArgFns = hasAnyName( + "::acos", "::acosh", "::asin", "::asinh", "::atan", "::atanh", "::cbrt", + "::ceil", "::cos", "::cosh", "::erf", "::erfc", "::exp", "::exp2", + "::expm1", "::fabs", "::floor", "::ilogb", "::lgamma", "::llrint", + "::log", "::log10", "::log1p", "::log2", "::logb", "::lrint", "::modf", + "::nearbyint", "::rint", "::round", "::sin", "::sinh", "::sqrt", "::tan", + "::tanh", "::tgamma", "::trunc", "::llround", "::lround"); + Finder->addMatcher( + callExpr(callee(functionDecl(OneDoubleArgFns, parameterCountIs(1), + hasBuiltinTyParam(0, DoubleTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to foo(double, double) where both args are floats. + auto TwoDoubleArgFns = hasAnyName("::atan2", "::copysign", "::fdim", "::fmax", + "::fmin", "::fmod", "::hypot", "::ldexp", + "::nextafter", "::pow", "::remainder"); + Finder->addMatcher( + callExpr(callee(functionDecl(TwoDoubleArgFns, parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, DoubleTy))), + hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy)) + .bind("call"), + this); + + // Match calls to fma(double, double, double) where all args are floats. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::fma"), parameterCountIs(3), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, DoubleTy), + hasBuiltinTyParam(2, DoubleTy))), + hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy), + hasBuiltinTyArg(2, FloatTy)) + .bind("call"), + this); + + // Match calls to frexp(double, int*) where the first arg is a float. + Finder->addMatcher( + callExpr(callee(functionDecl( + hasName("::frexp"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasParameter(1, parmVarDecl(hasType(pointerType( + pointee(isBuiltinType(IntTy)))))))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to nexttoward(double, long double) where the first arg is a + // float. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::nexttoward"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, LongDoubleTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to remquo(double, double, int*) where the first two args are + // floats. + Finder->addMatcher( + callExpr( + callee(functionDecl( + hasName("::remquo"), parameterCountIs(3), + hasBuiltinTyParam(0, DoubleTy), hasBuiltinTyParam(1, DoubleTy), + hasParameter(2, parmVarDecl(hasType(pointerType( + pointee(isBuiltinType(IntTy)))))))), + hasBuiltinTyArg(0, FloatTy), hasBuiltinTyArg(1, FloatTy)) + .bind("call"), + this); + + // Match calls to scalbln(double, long) where the first arg is a float. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::scalbln"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, LongTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // Match calls to scalbn(double, int) where the first arg is a float. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::scalbn"), parameterCountIs(2), + hasBuiltinTyParam(0, DoubleTy), + hasBuiltinTyParam(1, IntTy))), + hasBuiltinTyArg(0, FloatTy)) + .bind("call"), + this); + + // modf(double, double*) is omitted because the second parameter forces the + // type -- there's no conversion from float* to double*. +} + +void TypePromotionInMathFnCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + assert(Call != nullptr); + + StringRef OldFnName = Call->getDirectCallee()->getName(); + + // In C++ mode, we prefer std::foo to ::foof. But some of these suggestions + // are only valid in C++11 and newer. + static llvm::StringSet<> Cpp11OnlyFns = { + "acosh", "asinh", "atanh", "cbrt", "copysign", "erf", + "erfc", "exp2", "expm1", "fdim", "fma", "fmax", + "fmin", "hypot", "ilogb", "lgamma", "llrint", "llround", + "log1p", "log2", "logb", "lrint", "lround", "nearbyint", + "nextafter", "nexttoward", "remainder", "remquo", "rint", "round", + "scalbln", "scalbn", "tgamma", "trunc"}; + bool StdFnRequiresCpp11 = Cpp11OnlyFns.count(OldFnName); + + std::string NewFnName; + bool FnInCmath = false; + if (getLangOpts().CPlusPlus && + (!StdFnRequiresCpp11 || getLangOpts().CPlusPlus11)) { + NewFnName = ("std::" + OldFnName).str(); + FnInCmath = true; + } else { + NewFnName = (OldFnName + "f").str(); + } + + auto Diag = diag(Call->getExprLoc(), "call to '%0' promotes float to double") + << OldFnName + << FixItHint::CreateReplacement( + Call->getCallee()->getSourceRange(), NewFnName); + + // Suggest including if the function we're suggesting is declared in + // and it's not already included. We never have to suggest including + // , because the functions we're suggesting moving away from are all + // declared in . + if (FnInCmath) + if (auto IncludeFixit = IncludeInserter->CreateIncludeInsertion( + Result.Context->getSourceManager().getFileID(Call->getLocStart()), + "cmath", /*IsAngled=*/true)) + Diag << *IncludeFixit; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/TypePromotionInMathFnCheck.h b/clang-tidy/performance/TypePromotionInMathFnCheck.h new file mode 100644 index 000000000..22429570d --- /dev/null +++ b/clang-tidy/performance/TypePromotionInMathFnCheck.h @@ -0,0 +1,47 @@ +//===--- TypePromotionInMathFnCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_TYPE_PROMOTION_IN_MATH_FN_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_TYPE_PROMOTION_IN_MATH_FN_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// Finds calls to C math library functions with implicit float to double +/// promotions. +/// +/// For example, warns on ::sin(0.f), because this funciton's parameter is a +/// double. You probably meant to call std::sin(0.f) (in C++), or sinf(0.f) (in +/// C). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-type-promotion-in-math-fn.html +class TypePromotionInMathFnCheck : public ClangTidyCheck { +public: + TypePromotionInMathFnCheck(StringRef Name, ClangTidyContext *Context); + + void registerPPCallbacks(CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::unique_ptr IncludeInserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_TYPE_PROMOTION_IN_MATH_FN_H diff --git a/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tidy/performance/UnnecessaryCopyInitialization.cpp new file mode 100644 index 000000000..177497c41 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -0,0 +1,149 @@ +//===--- UnnecessaryCopyInitialization.cpp - clang-tidy--------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryCopyInitialization.h" + +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/Matchers.h" + +namespace clang { +namespace tidy { +namespace performance { +namespace { + +void recordFixes(const VarDecl &Var, ASTContext &Context, + DiagnosticBuilder &Diagnostic) { + Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); + if (!Var.getType().isLocalConstQualified()) + Diagnostic << utils::fixit::changeVarDeclToConst(Var); +} + +} // namespace + +using namespace ::clang::ast_matchers; +using utils::decl_ref_expr::isOnlyUsedAsConst; + +void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) { + auto ConstReference = referenceType(pointee(qualType(isConstQualified()))); + auto ConstOrConstReference = + allOf(anyOf(ConstReference, isConstQualified()), + unless(allOf(pointerType(), unless(pointerType(pointee( + qualType(isConstQualified()))))))); + + // Match method call expressions where the `this` argument is only used as + // const, this will be checked in `check()` part. This returned const + // reference is highly likely to outlive the local const reference of the + // variable being declared. The assumption is that the const reference being + // returned either points to a global static variable or to a member of the + // called object. + auto ConstRefReturningMethodCall = + cxxMemberCallExpr(callee(cxxMethodDecl(returns(ConstReference))), + on(declRefExpr(to(varDecl().bind("objectArg"))))); + auto ConstRefReturningFunctionCall = + callExpr(callee(functionDecl(returns(ConstReference))), + unless(callee(cxxMethodDecl()))); + + auto localVarCopiedFrom = [](const internal::Matcher &CopyCtorArg) { + return compoundStmt( + forEachDescendant( + declStmt( + has(varDecl(hasLocalStorage(), + hasType(matchers::isExpensiveToCopy()), + unless(isImplicit()), + hasInitializer( + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl( + isCopyConstructor())), + hasArgument(0, CopyCtorArg)) + .bind("ctorCall"))) + .bind("newVarDecl"))) + .bind("declStmt"))) + .bind("blockStmt"); + }; + + Finder->addMatcher(localVarCopiedFrom(anyOf(ConstRefReturningFunctionCall, + ConstRefReturningMethodCall)), + this); + + Finder->addMatcher(localVarCopiedFrom(declRefExpr( + to(varDecl(hasLocalStorage()).bind("oldVarDecl")))), + this); +} + +void UnnecessaryCopyInitialization::check( + const MatchFinder::MatchResult &Result) { + const auto *NewVar = Result.Nodes.getNodeAs("newVarDecl"); + const auto *OldVar = Result.Nodes.getNodeAs("oldVarDecl"); + const auto *ObjectArg = Result.Nodes.getNodeAs("objectArg"); + const auto *BlockStmt = Result.Nodes.getNodeAs("blockStmt"); + const auto *CtorCall = Result.Nodes.getNodeAs("ctorCall"); + // Do not propose fixes if the DeclStmt has multiple VarDecls or in macros + // since we cannot place them correctly. + bool IssueFix = + Result.Nodes.getNodeAs("declStmt")->isSingleDecl() && + !NewVar->getLocation().isMacroID(); + + // A constructor that looks like T(const T& t, bool arg = false) counts as a + // copy only when it is called with default arguments for the arguments after + // the first. + for (unsigned int i = 1; i < CtorCall->getNumArgs(); ++i) + if (!CtorCall->getArg(i)->isDefaultArgument()) + return; + + if (OldVar == nullptr) { + handleCopyFromMethodReturn(*NewVar, *BlockStmt, IssueFix, ObjectArg, + *Result.Context); + } else { + handleCopyFromLocalVar(*NewVar, *OldVar, *BlockStmt, IssueFix, + *Result.Context); + } +} + +void UnnecessaryCopyInitialization::handleCopyFromMethodReturn( + const VarDecl &Var, const Stmt &BlockStmt, bool IssueFix, + const VarDecl *ObjectArg, ASTContext &Context) { + bool IsConstQualified = Var.getType().isConstQualified(); + if (!IsConstQualified && !isOnlyUsedAsConst(Var, BlockStmt, Context)) + return; + if (ObjectArg != nullptr && + !isOnlyUsedAsConst(*ObjectArg, BlockStmt, Context)) + return; + + auto Diagnostic = + diag(Var.getLocation(), + IsConstQualified ? "the const qualified variable %0 is " + "copy-constructed from a const reference; " + "consider making it a const reference" + : "the variable %0 is copy-constructed from a " + "const reference but is only used as const " + "reference; consider making it a const reference") + << &Var; + if (IssueFix) + recordFixes(Var, Context, Diagnostic); +} + +void UnnecessaryCopyInitialization::handleCopyFromLocalVar( + const VarDecl &NewVar, const VarDecl &OldVar, const Stmt &BlockStmt, + bool IssueFix, ASTContext &Context) { + if (!isOnlyUsedAsConst(NewVar, BlockStmt, Context) || + !isOnlyUsedAsConst(OldVar, BlockStmt, Context)) + return; + + auto Diagnostic = diag(NewVar.getLocation(), + "local copy %0 of the variable %1 is never modified; " + "consider avoiding the copy") + << &NewVar << &OldVar; + if (IssueFix) + recordFixes(NewVar, Context, Diagnostic); +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/UnnecessaryCopyInitialization.h b/clang-tidy/performance/UnnecessaryCopyInitialization.h new file mode 100644 index 000000000..8ed414273 --- /dev/null +++ b/clang-tidy/performance/UnnecessaryCopyInitialization.h @@ -0,0 +1,47 @@ +//===--- UnnecessaryCopyInitialization.h - clang-tidy------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace performance { + +// The check detects local variable declarations that are copy initialized with +// the const reference of a function call or the const reference of a method +// call whose object is guaranteed to outlive the variable's scope and suggests +// to use a const reference. +// +// The check currently only understands a subset of variables that are +// guaranteed to outlive the const reference returned, namely: const variables, +// const references, and const pointers to const. +class UnnecessaryCopyInitialization : public ClangTidyCheck { +public: + UnnecessaryCopyInitialization(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCopyFromMethodReturn(const VarDecl &Var, const Stmt &BlockStmt, + bool IssueFix, const VarDecl *ObjectArg, + ASTContext &Context); + void handleCopyFromLocalVar(const VarDecl &NewVar, const VarDecl &OldVar, + const Stmt &BlockStmt, bool IssueFix, + ASTContext &Context); +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_INITIALIZATION_H diff --git a/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp new file mode 100644 index 000000000..4c123181f --- /dev/null +++ b/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -0,0 +1,190 @@ +//===--- UnnecessaryValueParamCheck.cpp - clang-tidy-----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryValueParamCheck.h" + +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/Matchers.h" +#include "../utils/TypeTraits.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { + +std::string paramNameOrIndex(StringRef Name, size_t Index) { + return (Name.empty() ? llvm::Twine('#') + llvm::Twine(Index + 1) + : llvm::Twine('\'') + Name + llvm::Twine('\'')) + .str(); +} + +template +bool isSubset(const S &SubsetCandidate, const S &SupersetCandidate) { + for (const auto &E : SubsetCandidate) + if (SupersetCandidate.count(E) == 0) + return false; + return true; +} + +bool isReferencedOutsideOfCallExpr(const FunctionDecl &Function, + ASTContext &Context) { + auto Matches = match(declRefExpr(to(functionDecl(equalsNode(&Function))), + unless(hasAncestor(callExpr()))), + Context); + return !Matches.empty(); +} + +bool hasLoopStmtAncestor(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context) { + auto Matches = + match(decl(forEachDescendant(declRefExpr( + equalsNode(&DeclRef), + unless(hasAncestor(stmt(anyOf(forStmt(), cxxForRangeStmt(), + whileStmt(), doStmt()))))))), + Decl, Context); + return Matches.empty(); +} + +} // namespace + +UnnecessaryValueParamCheck::UnnecessaryValueParamCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeStyle(utils::IncludeSorter::parseIncludeStyle( + Options.get("IncludeStyle", "llvm"))) {} + +void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) { + const auto ExpensiveValueParamDecl = + parmVarDecl(hasType(hasCanonicalType(allOf(matchers::isExpensiveToCopy(), + unless(referenceType())))), + decl().bind("param")); + Finder->addMatcher( + functionDecl(hasBody(stmt()), isDefinition(), + unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))), + unless(isInstantiated()), + has(typeLoc(forEach(ExpensiveValueParamDecl))), + decl().bind("functionDecl")), + this); +} + +void UnnecessaryValueParamCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Param = Result.Nodes.getNodeAs("param"); + const auto *Function = Result.Nodes.getNodeAs("functionDecl"); + const size_t Index = std::find(Function->parameters().begin(), + Function->parameters().end(), Param) - + Function->parameters().begin(); + bool IsConstQualified = + Param->getType().getCanonicalType().isConstQualified(); + + auto AllDeclRefExprs = utils::decl_ref_expr::allDeclRefExprs( + *Param, *Function, *Result.Context); + auto ConstDeclRefExprs = utils::decl_ref_expr::constReferenceDeclRefExprs( + *Param, *Function, *Result.Context); + + // Do not trigger on non-const value parameters when they are not only used as + // const. + if (!isSubset(AllDeclRefExprs, ConstDeclRefExprs)) + return; + + // If the parameter is non-const, check if it has a move constructor and is + // only referenced once to copy-construct another object or whether it has a + // move assignment operator and is only referenced once when copy-assigned. + // In this case wrap DeclRefExpr with std::move() to avoid the unnecessary + // copy. + if (!IsConstQualified && AllDeclRefExprs.size() == 1) { + auto CanonicalType = Param->getType().getCanonicalType(); + const auto &DeclRefExpr = **AllDeclRefExprs.begin(); + + if (!hasLoopStmtAncestor(DeclRefExpr, *Function, *Result.Context) && + ((utils::type_traits::hasNonTrivialMoveConstructor(CanonicalType) && + utils::decl_ref_expr::isCopyConstructorArgument( + DeclRefExpr, *Function, *Result.Context)) || + (utils::type_traits::hasNonTrivialMoveAssignment(CanonicalType) && + utils::decl_ref_expr::isCopyAssignmentArgument( + DeclRefExpr, *Function, *Result.Context)))) { + handleMoveFix(*Param, DeclRefExpr, *Result.Context); + return; + } + } + + auto Diag = + diag(Param->getLocation(), + IsConstQualified ? "the const qualified parameter %0 is " + "copied for each invocation; consider " + "making it a reference" + : "the parameter %0 is copied for each " + "invocation but only used as a const reference; " + "consider making it a const reference") + << paramNameOrIndex(Param->getName(), Index); + // Do not propose fixes when: + // 1. the ParmVarDecl is in a macro, since we cannot place them correctly + // 2. the function is virtual as it might break overrides + // 3. the function is referenced outside of a call expression within the + // compilation unit as the signature change could introduce build errors. + const auto *Method = llvm::dyn_cast(Function); + if (Param->getLocStart().isMacroID() || (Method && Method->isVirtual()) || + isReferencedOutsideOfCallExpr(*Function, *Result.Context)) + return; + for (const auto *FunctionDecl = Function; FunctionDecl != nullptr; + FunctionDecl = FunctionDecl->getPreviousDecl()) { + const auto &CurrentParam = *FunctionDecl->getParamDecl(Index); + Diag << utils::fixit::changeVarDeclToReference(CurrentParam, + *Result.Context); + // The parameter of each declaration needs to be checked individually as to + // whether it is const or not as constness can differ between definition and + // declaration. + if (!CurrentParam.getType().getCanonicalType().isConstQualified()) + Diag << utils::fixit::changeVarDeclToConst(CurrentParam); + } +} + +void UnnecessaryValueParamCheck::registerPPCallbacks( + CompilerInstance &Compiler) { + Inserter.reset(new utils::IncludeInserter( + Compiler.getSourceManager(), Compiler.getLangOpts(), IncludeStyle)); + Compiler.getPreprocessor().addPPCallbacks(Inserter->CreatePPCallbacks()); +} + +void UnnecessaryValueParamCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", + utils::IncludeSorter::toString(IncludeStyle)); +} + +void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Var, + const DeclRefExpr &CopyArgument, + const ASTContext &Context) { + auto Diag = diag(CopyArgument.getLocStart(), + "parameter %0 is passed by value and only copied once; " + "consider moving it to avoid unnecessary copies") + << &Var; + // Do not propose fixes in macros since we cannot place them correctly. + if (CopyArgument.getLocStart().isMacroID()) + return; + const auto &SM = Context.getSourceManager(); + auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM, + Context.getLangOpts()); + Diag << FixItHint::CreateInsertion(CopyArgument.getLocStart(), "std::move(") + << FixItHint::CreateInsertion(EndLoc, ")"); + if (auto IncludeFixit = Inserter->CreateIncludeInsertion( + SM.getFileID(CopyArgument.getLocStart()), "utility", + /*IsAngled=*/true)) + Diag << *IncludeFixit; +} + +} // namespace performance +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/performance/UnnecessaryValueParamCheck.h b/clang-tidy/performance/UnnecessaryValueParamCheck.h new file mode 100644 index 000000000..cbf0a3bbe --- /dev/null +++ b/clang-tidy/performance/UnnecessaryValueParamCheck.h @@ -0,0 +1,45 @@ +//===--- UnnecessaryValueParamCheck.h - clang-tidy---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_VALUE_PARAM_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_VALUE_PARAM_H + +#include "../ClangTidy.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace performance { + +/// \brief A check that flags value parameters of expensive to copy types that +/// can safely be converted to const references. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance-unnecessary-value-param.html +class UnnecessaryValueParamCheck : public ClangTidyCheck { +public: + UnnecessaryValueParamCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(CompilerInstance &Compiler) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + void handleMoveFix(const ParmVarDecl &Var, const DeclRefExpr &CopyArgument, + const ASTContext &Context); + + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_VALUE_PARAM_H diff --git a/clang-tidy/plugin/CMakeLists.txt b/clang-tidy/plugin/CMakeLists.txt new file mode 100644 index 000000000..5106d3f12 --- /dev/null +++ b/clang-tidy/plugin/CMakeLists.txt @@ -0,0 +1,22 @@ +add_clang_library(clangTidyPlugin + ClangTidyPlugin.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangSema + clangTidy + clangTidyBoostModule + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyMPIModule + clangTidyPerformanceModule + clangTidyReadabilityModule + clangTooling + ) diff --git a/clang-tidy/plugin/ClangTidyPlugin.cpp b/clang-tidy/plugin/ClangTidyPlugin.cpp new file mode 100644 index 000000000..1e6346c2e --- /dev/null +++ b/clang-tidy/plugin/ClangTidyPlugin.cpp @@ -0,0 +1,127 @@ +//===- ClangTidyPlugin.cpp - clang-tidy as a clang plugin -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/Frontend/MultiplexConsumer.h" + +namespace clang { +namespace tidy { + +/// The core clang tidy plugin action. This just provides the AST consumer and +/// command line flag parsing for using clang-tidy as a clang plugin. +class ClangTidyPluginAction : public PluginASTAction { + /// Wrapper to grant the context the same lifetime as the action. We use + /// MultiplexConsumer to avoid writing out all the forwarding methods. + class WrapConsumer : public MultiplexConsumer { + std::unique_ptr Context; + + public: + WrapConsumer(std::unique_ptr Context, + std::vector> Consumer) + : MultiplexConsumer(std::move(Consumer)), Context(std::move(Context)) {} + }; + +public: + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + // Insert the current diagnostics engine. + Context->setDiagnosticsEngine(&Compiler.getDiagnostics()); + + // Create the AST consumer. + ClangTidyASTConsumerFactory Factory(*Context); + std::vector> Vec; + Vec.push_back(Factory.CreateASTConsumer(Compiler, File)); + + return llvm::make_unique(std::move(Context), std::move(Vec)); + } + + bool ParseArgs(const CompilerInstance &, + const std::vector &Args) override { + ClangTidyGlobalOptions GlobalOptions; + ClangTidyOptions DefaultOptions; + ClangTidyOptions OverrideOptions; + + // Parse the extra command line args. + // FIXME: This is very limited at the moment. + for (StringRef Arg : Args) + if (Arg.startswith("-checks=")) + OverrideOptions.Checks = Arg.substr(strlen("-checks=")); + + auto Options = llvm::make_unique( + GlobalOptions, DefaultOptions, OverrideOptions); + Context = llvm::make_unique(std::move(Options)); + return true; + } + +private: + std::unique_ptr Context; +}; +} // namespace tidy +} // namespace clang + +// This anchor is used to force the linker to link in the generated object file +// and thus register the clang-tidy plugin. +volatile int ClangTidyPluginAnchorSource = 0; + +static clang::FrontendPluginRegistry::Add + X("clang-tidy", "clang-tidy"); + +namespace clang { +namespace tidy { + +// This anchor is used to force the linker to link the CERTModule. +extern volatile int CERTModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = + CERTModuleAnchorSource; + +// This anchor is used to force the linker to link the LLVMModule. +extern volatile int LLVMModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = + LLVMModuleAnchorSource; + +// This anchor is used to force the linker to link the CppCoreGuidelinesModule. +extern volatile int CppCoreGuidelinesModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination = + CppCoreGuidelinesModuleAnchorSource; + +// This anchor is used to force the linker to link the GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// This anchor is used to force the linker to link the MiscModule. +extern volatile int MiscModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MiscModuleAnchorDestination = + MiscModuleAnchorSource; + +// This anchor is used to force the linker to link the ModernizeModule. +extern volatile int ModernizeModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ModernizeModuleAnchorDestination = + ModernizeModuleAnchorSource; + +// This anchor is used to force the linker to link the MPIModule. +extern volatile int MPIModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination = + MPIModuleAnchorSource; + +// This anchor is used to force the linker to link the PerformanceModule. +extern volatile int PerformanceModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination = + PerformanceModuleAnchorSource; + +// This anchor is used to force the linker to link the ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/AvoidConstParamsInDecls.cpp b/clang-tidy/readability/AvoidConstParamsInDecls.cpp new file mode 100644 index 000000000..70329e83b --- /dev/null +++ b/clang-tidy/readability/AvoidConstParamsInDecls.cpp @@ -0,0 +1,122 @@ +//===--- AvoidConstParamsInDecls.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AvoidConstParamsInDecls.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/Optional.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { +namespace { + +SourceRange getTypeRange(const ParmVarDecl &Param) { + if (Param.getIdentifier() != nullptr) + return SourceRange(Param.getLocStart(), + Param.getLocEnd().getLocWithOffset(-1)); + return Param.getSourceRange(); +} + +} // namespace + +void AvoidConstParamsInDecls::registerMatchers(MatchFinder *Finder) { + const auto ConstParamDecl = + parmVarDecl(hasType(qualType(isConstQualified()))).bind("param"); + Finder->addMatcher( + functionDecl(unless(isDefinition()), + // Lambdas are always their own definition, but they + // generate a non-definition FunctionDecl too. Ignore those. + // Class template instantiations have a non-definition + // CXXMethodDecl for methods that aren't used in this + // translation unit. Ignore those, as the template will have + // already been checked. + unless(cxxMethodDecl(ofClass(cxxRecordDecl(anyOf( + isLambda(), ast_matchers::isTemplateInstantiation()))))), + has(typeLoc(forEach(ConstParamDecl)))) + .bind("func"), + this); +} + +// Re-lex the tokens to get precise location of last 'const' +static llvm::Optional ConstTok(CharSourceRange Range, + const MatchFinder::MatchResult &Result) { + const SourceManager &Sources = *Result.SourceManager; + std::pair LocInfo = + Sources.getDecomposedLoc(Range.getBegin()); + StringRef File = Sources.getBufferData(LocInfo.first); + const char *TokenBegin = File.data() + LocInfo.second; + Lexer RawLexer(Sources.getLocForStartOfFile(LocInfo.first), + Result.Context->getLangOpts(), File.begin(), TokenBegin, + File.end()); + Token Tok; + llvm::Optional ConstTok; + while (!RawLexer.LexFromRawLexer(Tok)) { + if (Sources.isBeforeInTranslationUnit(Range.getEnd(), Tok.getLocation())) + break; + if (Tok.is(tok::raw_identifier)) { + IdentifierInfo &Info = Result.Context->Idents.get(StringRef( + Sources.getCharacterData(Tok.getLocation()), Tok.getLength())); + Tok.setIdentifierInfo(&Info); + Tok.setKind(Info.getTokenID()); + } + if (Tok.is(tok::kw_const)) + ConstTok = Tok; + } + return ConstTok; +} + +void AvoidConstParamsInDecls::check(const MatchFinder::MatchResult &Result) { + const auto *Func = Result.Nodes.getNodeAs("func"); + const auto *Param = Result.Nodes.getNodeAs("param"); + + if (!Param->getType().isLocalConstQualified()) + return; + + auto Diag = diag(Param->getLocStart(), + "parameter %0 is const-qualified in the function " + "declaration; const-qualification of parameters only has an " + "effect in function definitions"); + if (Param->getName().empty()) { + for (unsigned int i = 0; i < Func->getNumParams(); ++i) { + if (Param == Func->getParamDecl(i)) { + Diag << (i + 1); + break; + } + } + } else { + Diag << Param; + } + + if (Param->getLocStart().isMacroID() != Param->getLocEnd().isMacroID()) { + // Do not offer a suggestion if the part of the variable declaration comes + // from a macro. + return; + } + + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(getTypeRange(*Param)), + *Result.SourceManager, getLangOpts()); + + if (!FileRange.isValid()) + return; + + auto Tok = ConstTok(FileRange, Result); + if (!Tok) + return; + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Tok->getLocation(), Tok->getLocation())); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/AvoidConstParamsInDecls.h b/clang-tidy/readability/AvoidConstParamsInDecls.h new file mode 100644 index 000000000..f2d91e82b --- /dev/null +++ b/clang-tidy/readability/AvoidConstParamsInDecls.h @@ -0,0 +1,34 @@ +//===--- AvoidConstParamsInDecls.h - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +// Detect function declarations that have const value parameters and discourage +// them. +class AvoidConstParamsInDecls : public ClangTidyCheck { +public: + AvoidConstParamsInDecls(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H diff --git a/clang-tidy/readability/BracesAroundStatementsCheck.cpp b/clang-tidy/readability/BracesAroundStatementsCheck.cpp new file mode 100644 index 000000000..4dea41dfb --- /dev/null +++ b/clang-tidy/readability/BracesAroundStatementsCheck.cpp @@ -0,0 +1,277 @@ +//===--- 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(*FullSourceLoc(Loc, SM).getCharacterData())) + 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 = LastTokenLoc; + // 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 || isStringLiteral(TokKind)) { + // 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(*FullSourceLoc(Loc, SM).getCharacterData())) + Loc = Loc.getLocWithOffset(1); + + if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) { + // 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(), SourceLocation()); + } + } 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..dc3fc6fae --- /dev/null +++ b/clang-tidy/readability/CMakeLists.txt @@ -0,0 +1,38 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyReadabilityModule + AvoidConstParamsInDecls.cpp + BracesAroundStatementsCheck.cpp + ContainerSizeEmptyCheck.cpp + DeleteNullPointerCheck.cpp + DeletedDefaultCheck.cpp + ElseAfterReturnCheck.cpp + FunctionSizeCheck.cpp + IdentifierNamingCheck.cpp + ImplicitBoolCastCheck.cpp + InconsistentDeclarationParameterNameCheck.cpp + MisplacedArrayIndexCheck.cpp + NamedParameterCheck.cpp + NamespaceCommentCheck.cpp + NonConstParameterCheck.cpp + ReadabilityTidyModule.cpp + RedundantControlFlowCheck.cpp + RedundantDeclarationCheck.cpp + RedundantFunctionPtrDereferenceCheck.cpp + RedundantMemberInitCheck.cpp + RedundantStringCStrCheck.cpp + RedundantSmartptrGetCheck.cpp + RedundantStringInitCheck.cpp + SimplifyBooleanExprCheck.cpp + StaticDefinitionInAnonymousNamespaceCheck.cpp + UniqueptrDeleteReleaseCheck.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + clangTidyUtils + clangTooling + ) diff --git a/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp new file mode 100644 index 000000000..665d1dbe3 --- /dev/null +++ b/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -0,0 +1,166 @@ +//===--- 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/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 { + +ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + const auto ValidContainer = cxxRecordDecl(isSameOrDerivedFrom( + namedDecl( + has(cxxMethodDecl( + isConst(), parameterCountIs(0), isPublic(), hasName("size"), + returns(qualType(isInteger(), unless(booleanType())))) + .bind("size")), + has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(), + hasName("empty"), returns(booleanType())) + .bind("empty"))) + .bind("container"))); + + const auto WrongUse = anyOf( + hasParent(binaryOperator( + matchers::isComparisonOperator(), + hasEitherOperand(ignoringImpCasts(anyOf( + integerLiteral(equals(1)), integerLiteral(equals(0)))))) + .bind("SizeBinaryOp")), + hasParent(implicitCastExpr( + hasImplicitDestinationType(booleanType()), + anyOf( + hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")), + anything()))), + hasParent(explicitCastExpr(hasDestinationType(booleanType())))); + + Finder->addMatcher( + cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer), + hasType(pointsTo(ValidContainer)), + hasType(references(ValidContainer)))) + .bind("STLObject")), + callee(cxxMethodDecl(hasName("size"))), WrongUse, + unless(hasAncestor(cxxMethodDecl( + ofClass(equalsBoundNode("container")))))) + .bind("SizeCallExpr"), + this); +} + +void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = + Result.Nodes.getNodeAs("SizeCallExpr"); + const auto *BinaryOp = Result.Nodes.getNodeAs("SizeBinaryOp"); + const auto *E = Result.Nodes.getNodeAs("STLObject"); + FixItHint Hint; + std::string ReplacementText = + Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), + *Result.SourceManager, getLangOpts()); + if (E->getType()->isPointerType()) + ReplacementText += "->empty()"; + else + ReplacementText += ".empty()"; + + 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); + } + + diag(MemberCall->getLocStart(), "the 'empty' method should be used to check " + "for emptiness instead of 'size'") + << 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..fc2bc503e --- /dev/null +++ b/clang-tidy/readability/DeleteNullPointerCheck.cpp @@ -0,0 +1,76 @@ +//===--- 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 PointerExpr = + ignoringImpCasts(declRefExpr(to(decl().bind("deletedPointer")))); + 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, + compoundStmt(has(DeleteExpr), 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(), + Lexer::getLocForEndOfToken(Compound->getLBracLoc(), 0, + *Result.SourceManager, + Result.Context->getLangOpts()))); + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + Compound->getRBracLoc(), + Lexer::getLocForEndOfToken(Compound->getRBracLoc(), 0, + *Result.SourceManager, + Result.Context->getLangOpts()))); + } +} + +} // 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..87ee46fe9 --- /dev/null +++ b/clang-tidy/readability/DeleteNullPointerCheck.h @@ -0,0 +1,35 @@ +//===--- DeleteNullPointerCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Check whether the 'if' statement is unnecessary before calling 'delete' on a pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-delete-null-pointer.html +class DeleteNullPointerCheck : public ClangTidyCheck { +public: + DeleteNullPointerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H diff --git a/clang-tidy/readability/DeletedDefaultCheck.cpp b/clang-tidy/readability/DeletedDefaultCheck.cpp new file mode 100644 index 000000000..a197e8dcd --- /dev/null +++ b/clang-tidy/readability/DeletedDefaultCheck.cpp @@ -0,0 +1,69 @@ +//===--- DeletedDefaultCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DeletedDefaultCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void DeletedDefaultCheck::registerMatchers(MatchFinder *Finder) { + // We match constructors/assignment operators that are: + // - explicitly marked '= default' + // - actually deleted + // - not in template instantiation. + // We bind the declaration to "method-decl" and also to "constructor" when + // it is a constructor. + + Finder->addMatcher( + cxxMethodDecl(anyOf(cxxConstructorDecl().bind("constructor"), + isCopyAssignmentOperator(), + isMoveAssignmentOperator()), + isDefaulted(), unless(isImplicit()), isDeleted(), + unless(isInstantiated())) + .bind("method-decl"), + this); +} + +void DeletedDefaultCheck::check(const MatchFinder::MatchResult &Result) { + const StringRef Message = "%0 is explicitly defaulted but implicitly " + "deleted, probably because %1; definition can " + "either be removed or explicitly deleted"; + if (const auto *Constructor = + Result.Nodes.getNodeAs("constructor")) { + auto Diag = diag(Constructor->getLocStart(), Message); + if (Constructor->isDefaultConstructor()) { + Diag << "default constructor" + << "a non-static data member or a base class is lacking a default " + "constructor"; + } else if (Constructor->isCopyConstructor()) { + Diag << "copy constructor" + << "a non-static data member or a base class is not copyable"; + } else if (Constructor->isMoveConstructor()) { + Diag << "move constructor" + << "a non-static data member or a base class is neither copyable " + "nor movable"; + } + } else if (const auto *Assignment = + Result.Nodes.getNodeAs("method-decl")) { + diag(Assignment->getLocStart(), Message) + << (Assignment->isCopyAssignmentOperator() ? "copy assignment operator" + : "move assignment operator") + << "a base class or a non-static data member is not assignable, e.g. " + "because the latter is marked 'const'"; + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/DeletedDefaultCheck.h b/clang-tidy/readability/DeletedDefaultCheck.h new file mode 100644 index 000000000..0608b07bf --- /dev/null +++ b/clang-tidy/readability/DeletedDefaultCheck.h @@ -0,0 +1,36 @@ +//===--- DeletedDefaultCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETED_DEFAULT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETED_DEFAULT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks when a constructor or an assignment operator is marked as '= default' +/// but is actually deleted by the compiler. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-deleted-default.html +class DeletedDefaultCheck : public ClangTidyCheck { +public: + DeletedDefaultCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETED_DEFAULT_H diff --git a/clang-tidy/readability/ElseAfterReturnCheck.cpp b/clang-tidy/readability/ElseAfterReturnCheck.cpp new file mode 100644 index 000000000..6c676636a --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.cpp @@ -0,0 +1,56 @@ +//===--- ElseAfterReturnCheck.cpp - clang-tidy-----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ElseAfterReturnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) { + const auto ControlFlowInterruptorMatcher = + stmt(anyOf(returnStmt().bind("return"), continueStmt().bind("continue"), + breakStmt().bind("break"), cxxThrowExpr().bind("throw"))); + Finder->addMatcher( + compoundStmt(forEach( + ifStmt(hasThen(stmt( + anyOf(ControlFlowInterruptorMatcher, + compoundStmt(has(ControlFlowInterruptorMatcher))))), + hasElse(stmt().bind("else"))) + .bind("if"))), + this); +} + +void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) { + const auto *If = Result.Nodes.getNodeAs("if"); + SourceLocation ElseLoc = If->getElseLoc(); + std::string ControlFlowInterruptor; + for (const auto *BindingName : {"return", "continue", "break", "throw"}) + if (Result.Nodes.getNodeAs(BindingName)) + ControlFlowInterruptor = BindingName; + + DiagnosticBuilder Diag = diag(ElseLoc, "do not use 'else' after '%0'") + << ControlFlowInterruptor; + Diag << tooling::fixit::createRemoval(ElseLoc); + + // FIXME: Removing the braces isn't always safe. Do a more careful analysis. + // FIXME: Change clang-format to correctly un-indent the code. + if (const auto *CS = Result.Nodes.getNodeAs("else")) + Diag << tooling::fixit::createRemoval(CS->getLBracLoc()) + << tooling::fixit::createRemoval(CS->getRBracLoc()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ElseAfterReturnCheck.h b/clang-tidy/readability/ElseAfterReturnCheck.h new file mode 100644 index 000000000..8479ab503 --- /dev/null +++ b/clang-tidy/readability/ElseAfterReturnCheck.h @@ -0,0 +1,34 @@ +//===--- ElseAfterReturnCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Flags the usages of `else` after `return`. +/// +/// http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return +class ElseAfterReturnCheck : public ClangTidyCheck { +public: + ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H diff --git a/clang-tidy/readability/FunctionSizeCheck.cpp b/clang-tidy/readability/FunctionSizeCheck.cpp new file mode 100644 index 000000000..d02972dde --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.cpp @@ -0,0 +1,134 @@ +//===--- FunctionSize.cpp - clang-tidy ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "FunctionSizeCheck.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +class FunctionASTVisitor : public RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + +public: + bool TraverseStmt(Stmt *Node) { + if (!Node) + return Base::TraverseStmt(Node); + + if (TrackedParent.back() && !isa(Node)) + ++Info.Statements; + + switch (Node->getStmtClass()) { + case Stmt::IfStmtClass: + case Stmt::WhileStmtClass: + case Stmt::DoStmtClass: + case Stmt::CXXForRangeStmtClass: + case Stmt::ForStmtClass: + case Stmt::SwitchStmtClass: + ++Info.Branches; + // fallthrough + case Stmt::CompoundStmtClass: + TrackedParent.push_back(true); + break; + default: + TrackedParent.push_back(false); + break; + } + + Base::TraverseStmt(Node); + + TrackedParent.pop_back(); + return true; + } + + bool TraverseDecl(Decl *Node) { + TrackedParent.push_back(false); + Base::TraverseDecl(Node); + TrackedParent.pop_back(); + return true; + } + + struct FunctionInfo { + FunctionInfo() : Lines(0), Statements(0), Branches(0) {} + unsigned Lines; + unsigned Statements; + unsigned Branches; + }; + FunctionInfo Info; + std::vector TrackedParent; +}; + +FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + LineThreshold(Options.get("LineThreshold", -1U)), + StatementThreshold(Options.get("StatementThreshold", 800U)), + BranchThreshold(Options.get("BranchThreshold", -1U)) {} + +void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "LineThreshold", LineThreshold); + Options.store(Opts, "StatementThreshold", StatementThreshold); + Options.store(Opts, "BranchThreshold", BranchThreshold); +} + +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.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()); + } + } + + if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || + FI.Branches > BranchThreshold) { + 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; + } +} + +} // 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..ed40330c3 --- /dev/null +++ b/clang-tidy/readability/FunctionSizeCheck.h @@ -0,0 +1,48 @@ +//===--- 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). +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; +}; + +} // 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..c1c2eb704 --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -0,0 +1,917 @@ +//===--- 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(NamingCheckId LHS, NamingCheckId RHS) { + if (RHS == getEmptyKey()) + return LHS == getEmptyKey(); + if (RHS == getTombstoneKey()) + return LHS == getTombstoneKey(); + return LHS == RHS; + } +}; +} // namespace llvm + +namespace clang { +namespace tidy { +namespace readability { + +// clang-format off +#define NAMING_KEYS(m) \ + m(Namespace) \ + m(InlineNamespace) \ + m(EnumConstant) \ + m(ConstexprVariable) \ + m(ConstantMember) \ + m(PrivateMember) \ + m(ProtectedMember) \ + m(PublicMember) \ + m(Member) \ + m(ClassConstant) \ + m(ClassMember) \ + m(GlobalConstant) \ + m(GlobalVariable) \ + m(LocalConstant) \ + m(LocalVariable) \ + m(StaticConstant) \ + m(StaticVariable) \ + m(Constant) \ + m(Variable) \ + m(ConstantParameter) \ + m(ParameterPack) \ + m(Parameter) \ + m(AbstractClass) \ + m(Struct) \ + m(Class) \ + m(Union) \ + m(Enum) \ + m(GlobalFunction) \ + m(ConstexprFunction) \ + m(Function) \ + m(ConstexprMethod) \ + m(VirtualMethod) \ + m(ClassMethod) \ + m(PrivateMethod) \ + m(ProtectedMethod) \ + m(PublicMethod) \ + m(Method) \ + m(Typedef) \ + m(TypeTemplateParameter) \ + m(ValueTemplateParameter) \ + m(TemplateTemplateParameter) \ + m(TemplateParameter) \ + m(TypeAlias) \ + m(MacroDefinition) \ + +enum StyleKind { +#define ENUMERATE(v) SK_ ## v, + NAMING_KEYS(ENUMERATE) +#undef ENUMERATE + SK_Count, + SK_Invalid +}; + +static StringRef const StyleNames[] = { +#define STRINGIZE(v) #v, + NAMING_KEYS(STRINGIZE) +#undef STRINGIZE +}; + +#undef NAMING_KEYS +// clang-format on + +namespace { +/// Callback supplies macros to IdentifierNamingCheck::checkMacro +class IdentifierNamingCheckPPCallbacks : public PPCallbacks { +public: + IdentifierNamingCheckPPCallbacks(Preprocessor *PP, + IdentifierNamingCheck *Check) + : PP(PP), Check(Check) {} + + /// MacroDefined calls checkMacro for macros in the main file + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override { + Check->checkMacro(PP->getSourceManager(), MacroNameTok, MD->getMacroInfo()); + } + + /// MacroExpands calls expandMacro for macros in the main file + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange /*Range*/, + const MacroArgs * /*Args*/) override { + Check->expandMacro(MacroNameTok, MD.getMacroInfo()); + } + +private: + Preprocessor *PP; + IdentifierNamingCheck *Check; +}; +} // namespace + +IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) { + auto const fromString = [](StringRef Str) { + return llvm::StringSwitch(Str) + .Case("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(CT_AnyCase); + }; + + for (auto const &Name : StyleNames) { + NamingStyles.push_back( + NamingStyle(fromString(Options.get((Name + "Case").str(), "")), + Options.get((Name + "Prefix").str(), ""), + Options.get((Name + "Suffix").str(), ""))); + } + + 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) { + 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; + + if (!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, + IdentifierNamingCheck::NamingStyle Style) { + return Style.Prefix + fixupWithCase(Name, Style.Case) + Style.Suffix; +} + +static StyleKind findStyleKind( + const NamedDecl *D, + const std::vector &NamingStyles) { + if (isa(D) && NamingStyles[SK_Typedef].isSet()) + return SK_Typedef; + + if (isa(D) && NamingStyles[SK_TypeAlias].isSet()) + return SK_TypeAlias; + + if (const auto *Decl = dyn_cast(D)) { + if (Decl->isAnonymousNamespace()) + return SK_Invalid; + + if (Decl->isInline() && NamingStyles[SK_InlineNamespace].isSet()) + return SK_InlineNamespace; + + if (NamingStyles[SK_Namespace].isSet()) + return SK_Namespace; + } + + if (isa(D) && NamingStyles[SK_Enum].isSet()) + return SK_Enum; + + if (isa(D)) { + if (NamingStyles[SK_EnumConstant].isSet()) + return SK_EnumConstant; + + if (NamingStyles[SK_Constant].isSet()) + 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].isSet()) + return SK_AbstractClass; + + if (Decl->isStruct() && NamingStyles[SK_Struct].isSet()) + return SK_Struct; + + if (Decl->isStruct() && NamingStyles[SK_Class].isSet()) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Class].isSet()) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Struct].isSet()) + return SK_Struct; + + if (Decl->isUnion() && NamingStyles[SK_Union].isSet()) + return SK_Union; + + if (Decl->isEnum() && NamingStyles[SK_Enum].isSet()) + return SK_Enum; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_ConstantMember].isSet()) + return SK_ConstantMember; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + if (Decl->getAccess() == AS_private && + NamingStyles[SK_PrivateMember].isSet()) + return SK_PrivateMember; + + if (Decl->getAccess() == AS_protected && + NamingStyles[SK_ProtectedMember].isSet()) + return SK_ProtectedMember; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMember].isSet()) + return SK_PublicMember; + + if (NamingStyles[SK_Member].isSet()) + return SK_Member; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable].isSet()) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_ConstantParameter].isSet()) + return SK_ConstantParameter; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + if (Decl->isParameterPack() && NamingStyles[SK_ParameterPack].isSet()) + return SK_ParameterPack; + + if (NamingStyles[SK_Parameter].isSet()) + return SK_Parameter; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable].isSet()) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant].isSet()) + return SK_ClassConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant].isSet()) + return SK_GlobalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isStaticLocal() && NamingStyles[SK_StaticConstant].isSet()) + return SK_StaticConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant].isSet()) + return SK_LocalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + Decl->isFunctionOrMethodVarDecl() && + NamingStyles[SK_LocalConstant].isSet()) + return SK_LocalConstant; + + if (!Type.isNull() && Type.isLocalConstQualified() && + NamingStyles[SK_Constant].isSet()) + return SK_Constant; + + if (Decl->isStaticDataMember() && NamingStyles[SK_ClassMember].isSet()) + return SK_ClassMember; + + if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalVariable].isSet()) + return SK_GlobalVariable; + + if (Decl->isStaticLocal() && NamingStyles[SK_StaticVariable].isSet()) + return SK_StaticVariable; + + if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalVariable].isSet()) + return SK_LocalVariable; + + if (Decl->isFunctionOrMethodVarDecl() && + NamingStyles[SK_LocalVariable].isSet()) + return SK_LocalVariable; + + if (NamingStyles[SK_Variable].isSet()) + 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].isSet()) + return SK_ConstexprMethod; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction].isSet()) + return SK_ConstexprFunction; + + if (Decl->isStatic() && NamingStyles[SK_ClassMethod].isSet()) + return SK_ClassMethod; + + if (Decl->isVirtual() && NamingStyles[SK_VirtualMethod].isSet()) + return SK_VirtualMethod; + + if (Decl->getAccess() == AS_private && + NamingStyles[SK_PrivateMethod].isSet()) + return SK_PrivateMethod; + + if (Decl->getAccess() == AS_protected && + NamingStyles[SK_ProtectedMethod].isSet()) + return SK_ProtectedMethod; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMethod].isSet()) + return SK_PublicMethod; + + if (NamingStyles[SK_Method].isSet()) + return SK_Method; + + if (NamingStyles[SK_Function].isSet()) + 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].isSet()) + return SK_ConstexprFunction; + + if (Decl->isGlobal() && NamingStyles[SK_GlobalFunction].isSet()) + return SK_GlobalFunction; + + if (NamingStyles[SK_Function].isSet()) + return SK_Function; + } + + if (isa(D)) { + if (NamingStyles[SK_TypeTemplateParameter].isSet()) + return SK_TypeTemplateParameter; + + if (NamingStyles[SK_TemplateParameter].isSet()) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa(D)) { + if (NamingStyles[SK_ValueTemplateParameter].isSet()) + return SK_ValueTemplateParameter; + + if (NamingStyles[SK_TemplateParameter].isSet()) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa(D)) { + if (NamingStyles[SK_TemplateTemplateParameter].isSet()) + return SK_TemplateTemplateParameter; + + if (NamingStyles[SK_TemplateParameter].isSet()) + 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; + + NamingStyle Style = NamingStyles[SK]; + StringRef Name = Decl->getName(); + if (matchesStyle(Name, Style)) + return; + + std::string KindName = fixupWithCase(StyleNames[SK], CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); + + std::string Fixup = fixupWithStyle(Name, Style); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + DEBUG(llvm::dbgs() + << Decl->getLocStart().printToString(*Result.SourceManager) + << llvm::format(": unable to split words for %s '%s'\n", + KindName.c_str(), Name.str().c_str())); + } + } else { + NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId( + Decl->getLocation(), Decl->getNameAsString())]; + SourceRange Range = + DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation()) + .getSourceRange(); + + Failure.Fixup = std::move(Fixup); + Failure.KindName = std::move(KindName); + addUsage(NamingCheckFailures, Decl, Range); + } + } +} + +void IdentifierNamingCheck::checkMacro(SourceManager &SourceMgr, + const Token &MacroNameTok, + const MacroInfo *MI) { + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + NamingStyle Style = NamingStyles[SK_MacroDefinition]; + if (matchesStyle(Name, Style)) + return; + + std::string KindName = + fixupWithCase(StyleNames[SK_MacroDefinition], CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); + + std::string Fixup = fixupWithStyle(Name, Style); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + DEBUG( + llvm::dbgs() << MacroNameTok.getLocation().printToString(SourceMgr) + << llvm::format(": unable to split words for %s '%s'\n", + KindName.c_str(), Name.str().c_str())); + } + } else { + NamingCheckId ID(MI->getDefinitionLoc(), Name); + NamingCheckFailure &Failure = NamingCheckFailures[ID]; + SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); + + Failure.Fixup = std::move(Fixup); + Failure.KindName = std::move(KindName); + addUsage(NamingCheckFailures, ID, Range); + } +} + +void IdentifierNamingCheck::expandMacro(const Token &MacroNameTok, + const MacroInfo *MI) { + StringRef Name = MacroNameTok.getIdentifierInfo()->getName(); + NamingCheckId ID(MI->getDefinitionLoc(), Name); + + auto Failure = NamingCheckFailures.find(ID); + if (Failure == NamingCheckFailures.end()) + return; + + SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc()); + addUsage(NamingCheckFailures, ID, Range); +} + +void IdentifierNamingCheck::onEndOfTranslationUnit() { + for (const auto &Pair : NamingCheckFailures) { + const NamingCheckId &Decl = Pair.first; + const NamingCheckFailure &Failure = Pair.second; + + if (Failure.KindName.empty()) + continue; + + if (Failure.ShouldFix) { + auto Diag = diag(Decl.first, "invalid case style for %0 '%1'") + << Failure.KindName << Decl.second; + + for (const auto &Loc : Failure.RawUsageLocs) { + // We assume that the identifier name is made of one token only. This is + // always the case as we ignore usages in macros that could build + // identifier names by combining multiple tokens. + // + // For destructors, we alread take care of it by remembering the + // location of the start of the identifier and not the start of the + // tilde. + // + // Other multi-token identifiers, such as operators are not checked at + // all. + Diag << FixItHint::CreateReplacement( + SourceRange(SourceLocation::getFromRawEncoding(Loc)), + Failure.Fixup); + } + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/IdentifierNamingCheck.h b/clang-tidy/readability/IdentifierNamingCheck.h new file mode 100644 index 000000000..5d2580911 --- /dev/null +++ b/clang-tidy/readability/IdentifierNamingCheck.h @@ -0,0 +1,113 @@ +//===--- 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() : Case(CT_AnyCase) {} + + NamingStyle(CaseType Case, const std::string &Prefix, + const std::string &Suffix) + : Case(Case), Prefix(Prefix), Suffix(Suffix) {} + + CaseType Case; + std::string Prefix; + std::string Suffix; + + bool isSet() const { + return !(Case == CT_AnyCase && Prefix.empty() && Suffix.empty()); + } + }; + + /// \brief Holds an identifier name check failure, tracking the kind of the + /// identifer, its possible fixup and the starting locations of all the + /// identifier usages. + struct NamingCheckFailure { + std::string KindName; + std::string Fixup; + + /// \brief Whether the failure should be fixed or not. + /// + /// ie: if the identifier was used or declared within a macro we won't offer + /// a fixup for safety reasons. + bool ShouldFix; + + /// \brief A set of all the identifier usages starting SourceLocation, in + /// their encoded form. + llvm::DenseSet RawUsageLocs; + + NamingCheckFailure() : ShouldFix(true) {} + }; + + typedef std::pair NamingCheckId; + + typedef llvm::DenseMap + NamingCheckFailureMap; + + /// Check Macros for style violations. + void checkMacro(SourceManager &sourceMgr, const Token &MacroNameTok, + const MacroInfo *MI); + + /// Add a usage of a macro if it already has a violation. + void expandMacro(const Token &MacroNameTok, const MacroInfo *MI); + +private: + std::vector NamingStyles; + bool IgnoreFailedSplit; + NamingCheckFailureMap NamingCheckFailures; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H diff --git a/clang-tidy/readability/ImplicitBoolCastCheck.cpp b/clang-tidy/readability/ImplicitBoolCastCheck.cpp new file mode 100644 index 000000000..3322cbf1f --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolCastCheck.cpp @@ -0,0 +1,432 @@ +//===--- ImplicitBoolCastCheck.cpp - clang-tidy----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ImplicitBoolCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +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) && + clang::Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL"; +} + +AST_MATCHER(Stmt, isNULLMacroExpansion) { + return isNULLMacroExpansion(&Node, Finder->getASTContext()); +} + +ast_matchers::internal::Matcher createExceptionCasesMatcher() { + return expr(anyOf(hasParent(explicitCastExpr()), + allOf(isMacroExpansion(), unless(isNULLMacroExpansion())), + isInTemplateInstantiation(), + hasAncestor(functionTemplateDecl()))); +} + +StatementMatcher createImplicitCastFromBoolMatcher() { + return implicitCastExpr( + unless(createExceptionCasesMatcher()), + 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(qualType(booleanType()))))); +} + +StringRef +getZeroLiteralToCompareWithForGivenType(CastKind CastExpressionKind, + QualType CastSubExpressionType, + ASTContext &Context) { + switch (CastExpressionKind) { + case CK_IntegralToBoolean: + return CastSubExpressionType->isUnsignedIntegerType() ? "0u" : "0"; + + case CK_FloatingToBoolean: + return Context.hasSameType(CastSubExpressionType, 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 *UnaryOperatorExpression = + llvm::dyn_cast(Statement); + return UnaryOperatorExpression != nullptr && + UnaryOperatorExpression->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 *OverloadedOperatorCall = + llvm::dyn_cast(Statement)) { + return areParensNeededForOverloadedOperator( + OverloadedOperatorCall->getOperator()); + } + + return llvm::isa(Statement) || + llvm::isa(Statement); +} + +void addFixItHintsForGenericExpressionCastToBool( + DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context) { + // In case of expressions like (! integer), we should remove the redundant not + // operator and use inverted comparison (integer == 0). + bool InvertComparison = + ParentStatement != nullptr && isUnaryLogicalNotOperator(ParentStatement); + if (InvertComparison) { + SourceLocation ParentStartLoc = ParentStatement->getLocStart(); + SourceLocation ParentEndLoc = + llvm::cast(ParentStatement)->getSubExpr()->getLocStart(); + Diagnostic.AddFixItHint(FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc))); + + auto FurtherParents = Context.getParents(*ParentStatement); + ParentStatement = FurtherParents[0].get(); + } + + const Expr *SubExpression = CastExpression->getSubExpr(); + + bool NeedInnerParens = areParensNeededForStatement(SubExpression); + bool NeedOuterParens = ParentStatement != nullptr && + areParensNeededForStatement(ParentStatement); + + std::string StartLocInsertion; + + if (NeedOuterParens) { + StartLocInsertion += "("; + } + if (NeedInnerParens) { + StartLocInsertion += "("; + } + + if (!StartLocInsertion.empty()) { + SourceLocation StartLoc = CastExpression->getLocStart(); + Diagnostic.AddFixItHint( + FixItHint::CreateInsertion(StartLoc, StartLocInsertion)); + } + + std::string EndLocInsertion; + + if (NeedInnerParens) { + EndLocInsertion += ")"; + } + + if (InvertComparison) { + EndLocInsertion += " == "; + } else { + EndLocInsertion += " != "; + } + + EndLocInsertion += getZeroLiteralToCompareWithForGivenType( + CastExpression->getCastKind(), SubExpression->getType(), Context); + + if (NeedOuterParens) { + EndLocInsertion += ")"; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + CastExpression->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, EndLocInsertion)); +} + +StringRef getEquivalentBoolLiteralForExpression(const Expr *Expression, + ASTContext &Context) { + if (isNULLMacroExpansion(Expression, Context)) { + return "false"; + } + + if (const auto *IntLit = llvm::dyn_cast(Expression)) { + return (IntLit->getValue() == 0) ? "false" : "true"; + } + + if (const auto *FloatLit = llvm::dyn_cast(Expression)) { + llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); + FloatLitAbsValue.clearSign(); + return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true"; + } + + if (const auto *CharLit = llvm::dyn_cast(Expression)) { + return (CharLit->getValue() == 0) ? "false" : "true"; + } + + if (llvm::isa(Expression->IgnoreCasts())) { + return "true"; + } + + return StringRef(); +} + +void addFixItHintsForLiteralCastToBool(DiagnosticBuilder &Diagnostic, + const ImplicitCastExpr *CastExpression, + StringRef EquivalentLiteralExpression) { + SourceLocation StartLoc = CastExpression->getLocStart(); + SourceLocation EndLoc = CastExpression->getLocEnd(); + + Diagnostic.AddFixItHint(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), + EquivalentLiteralExpression)); +} + +void addFixItHintsForGenericExpressionCastFromBool( + DiagnosticBuilder &Diagnostic, const ImplicitCastExpr *CastExpression, + ASTContext &Context, StringRef OtherType) { + const Expr *SubExpression = CastExpression->getSubExpr(); + bool NeedParens = !llvm::isa(SubExpression); + + std::string StartLocInsertion = "static_cast<"; + StartLocInsertion += OtherType.str(); + StartLocInsertion += ">"; + if (NeedParens) { + StartLocInsertion += "("; + } + + SourceLocation StartLoc = CastExpression->getLocStart(); + Diagnostic.AddFixItHint( + FixItHint::CreateInsertion(StartLoc, StartLocInsertion)); + + if (NeedParens) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + CastExpression->getLocEnd(), 0, Context.getSourceManager(), + Context.getLangOpts()); + + Diagnostic.AddFixItHint(FixItHint::CreateInsertion(EndLoc, ")")); + } +} + +StringRef getEquivalentLiteralForBoolLiteral( + const CXXBoolLiteralExpr *BoolLiteralExpression, QualType DestinationType, + ASTContext &Context) { + // Prior to C++11, false literal could be implicitly converted to pointer. + if (!Context.getLangOpts().CPlusPlus11 && + (DestinationType->isPointerType() || + DestinationType->isMemberPointerType()) && + BoolLiteralExpression->getValue() == false) { + return "0"; + } + + if (DestinationType->isFloatingType()) { + if (BoolLiteralExpression->getValue() == true) { + return Context.hasSameType(DestinationType, Context.FloatTy) ? "1.0f" + : "1.0"; + } + return Context.hasSameType(DestinationType, Context.FloatTy) ? "0.0f" + : "0.0"; + } + + if (BoolLiteralExpression->getValue() == true) { + return DestinationType->isUnsignedIntegerType() ? "1u" : "1"; + } + return DestinationType->isUnsignedIntegerType() ? "0u" : "0"; +} + +void addFixItHintsForLiteralCastFromBool(DiagnosticBuilder &Diagnostic, + const ImplicitCastExpr *CastExpression, + ASTContext &Context, + QualType DestinationType) { + SourceLocation StartLoc = CastExpression->getLocStart(); + SourceLocation EndLoc = CastExpression->getLocEnd(); + const auto *BoolLiteralExpression = + llvm::dyn_cast(CastExpression->getSubExpr()); + + Diagnostic.AddFixItHint(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(StartLoc, EndLoc), + getEquivalentLiteralForBoolLiteral(BoolLiteralExpression, DestinationType, + Context))); +} + +StatementMatcher createConditionalExpressionMatcher() { + return stmt(anyOf(ifStmt(), conditionalOperator(), + parenExpr(hasParent(conditionalOperator())))); +} + +bool isAllowedConditionalCast(const ImplicitCastExpr *CastExpression, + ASTContext &Context) { + auto AllowedConditionalMatcher = stmt(hasParent(stmt( + anyOf(createConditionalExpressionMatcher(), + unaryOperator(hasOperatorName("!"), + hasParent(createConditionalExpressionMatcher())))))); + + auto MatchResult = match(AllowedConditionalMatcher, *CastExpression, Context); + return !MatchResult.empty(); +} + +} // anonymous namespace + +ImplicitBoolCastCheck::ImplicitBoolCastCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowConditionalIntegerCasts( + Options.get("AllowConditionalIntegerCasts", false)), + AllowConditionalPointerCasts( + Options.get("AllowConditionalPointerCasts", false)) {} + +void ImplicitBoolCastCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowConditionalIntegerCasts", + AllowConditionalIntegerCasts); + Options.store(Opts, "AllowConditionalPointerCasts", + AllowConditionalPointerCasts); +} + +void ImplicitBoolCastCheck::registerMatchers(MatchFinder *Finder) { + // This check doesn't make much sense if we run it on language without + // built-in bool support. + if (!getLangOpts().Bool) { + return; + } + + Finder->addMatcher( + implicitCastExpr( + // Exclude cases common to implicit cast to and from bool. + unless(createExceptionCasesMatcher()), + // 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())))), + anyOf(hasCastKind(CK_IntegralToBoolean), + hasCastKind(CK_FloatingToBoolean), + hasCastKind(CK_PointerToBoolean), + hasCastKind(CK_MemberPointerToBoolean)), + // Retrive also parent statement, to check if we need additional + // parens in replacement. + anyOf(hasParent(stmt().bind("parentStmt")), anything())) + .bind("implicitCastToBool"), + this); + + Finder->addMatcher( + implicitCastExpr( + createImplicitCastFromBoolMatcher(), + // 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(hasOperatorName("=="), hasOperatorName("!=")), + hasLHS(createImplicitCastFromBoolMatcher()), + hasRHS(createImplicitCastFromBoolMatcher())))), + // Check also for nested casts, for example: bool -> int -> float. + anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), + anything())) + .bind("implicitCastFromBool"), + this); +} + +void ImplicitBoolCastCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *CastToBool = + Result.Nodes.getNodeAs("implicitCastToBool")) { + const auto *ParentStatement = Result.Nodes.getNodeAs("parentStmt"); + return handleCastToBool(CastToBool, ParentStatement, *Result.Context); + } + + if (const auto *CastFromBool = + Result.Nodes.getNodeAs("implicitCastFromBool")) { + const auto *FurtherImplicitCastExpression = + Result.Nodes.getNodeAs("furtherImplicitCast"); + return handleCastFromBool(CastFromBool, FurtherImplicitCastExpression, + *Result.Context); + } +} + +void ImplicitBoolCastCheck::handleCastToBool( + const ImplicitCastExpr *CastExpression, const Stmt *ParentStatement, + ASTContext &Context) { + if (AllowConditionalPointerCasts && + (CastExpression->getCastKind() == CK_PointerToBoolean || + CastExpression->getCastKind() == CK_MemberPointerToBoolean) && + isAllowedConditionalCast(CastExpression, Context)) { + return; + } + + if (AllowConditionalIntegerCasts && + CastExpression->getCastKind() == CK_IntegralToBoolean && + isAllowedConditionalCast(CastExpression, Context)) { + return; + } + + std::string OtherType = CastExpression->getSubExpr()->getType().getAsString(); + DiagnosticBuilder Diagnostic = + diag(CastExpression->getLocStart(), "implicit cast '%0' -> bool") + << OtherType; + + StringRef EquivalentLiteralExpression = getEquivalentBoolLiteralForExpression( + CastExpression->getSubExpr(), Context); + if (!EquivalentLiteralExpression.empty()) { + addFixItHintsForLiteralCastToBool(Diagnostic, CastExpression, + EquivalentLiteralExpression); + } else { + addFixItHintsForGenericExpressionCastToBool(Diagnostic, CastExpression, + ParentStatement, Context); + } +} + +void ImplicitBoolCastCheck::handleCastFromBool( + const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context) { + QualType DestinationType = (FurtherImplicitCastExpression != nullptr) + ? FurtherImplicitCastExpression->getType() + : CastExpression->getType(); + std::string DestinationTypeString = DestinationType.getAsString(); + DiagnosticBuilder Diagnostic = + diag(CastExpression->getLocStart(), "implicit cast bool -> '%0'") + << DestinationTypeString; + + if (llvm::isa(CastExpression->getSubExpr())) { + addFixItHintsForLiteralCastFromBool(Diagnostic, CastExpression, Context, + DestinationType); + } else { + addFixItHintsForGenericExpressionCastFromBool( + Diagnostic, CastExpression, Context, DestinationTypeString); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/ImplicitBoolCastCheck.h b/clang-tidy/readability/ImplicitBoolCastCheck.h new file mode 100644 index 000000000..cd8addfde --- /dev/null +++ b/clang-tidy/readability/ImplicitBoolCastCheck.h @@ -0,0 +1,46 @@ +//===--- ImplicitBoolCastCheck.h - clang-tidy--------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Checks for use of implicit bool casts in expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-implicit-bool-cast.html +class ImplicitBoolCastCheck : public ClangTidyCheck { +public: + ImplicitBoolCastCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCastToBool(const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context); + void handleCastFromBool(const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context); + + bool AllowConditionalIntegerCasts; + bool AllowConditionalPointerCasts; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CAST_H diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp new file mode 100644 index 000000000..d20416e80 --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -0,0 +1,339 @@ +//===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "InconsistentDeclarationParameterNameCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +AST_MATCHER(FunctionDecl, hasOtherDeclarations) { + auto It = Node.redecls_begin(); + auto EndIt = Node.redecls_end(); + + if (It == EndIt) + return false; + + ++It; + return It != EndIt; +} + +struct DifferingParamInfo { + DifferingParamInfo(StringRef SourceName, StringRef OtherName, + SourceRange OtherNameRange, bool GenerateFixItHint) + : SourceName(SourceName), OtherName(OtherName), + OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {} + + StringRef SourceName; + StringRef OtherName; + SourceRange OtherNameRange; + bool GenerateFixItHint; +}; + +using DifferingParamsContainer = llvm::SmallVector; + +struct InconsistentDeclarationInfo { + InconsistentDeclarationInfo(SourceLocation DeclarationLocation, + DifferingParamsContainer &&DifferingParams) + : DeclarationLocation(DeclarationLocation), + DifferingParams(std::move(DifferingParams)) {} + + SourceLocation DeclarationLocation; + DifferingParamsContainer DifferingParams; +}; + +using InconsistentDeclarationsContainer = + llvm::SmallVector; + +bool checkIfFixItHintIsApplicable( + const FunctionDecl *ParameterSourceDeclaration, + const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) { + // Assumptions with regard to function declarations/definition: + // * If both function declaration and definition are seen, assume that + // definition is most up-to-date, and use it to generate replacements. + // * If only function declarations are seen, there is no easy way to tell + // which is up-to-date and which is not, so don't do anything. + // TODO: This may be changed later, but for now it seems the reasonable + // solution. + if (!ParameterSourceDeclaration->isThisDeclarationADefinition()) + return false; + + // Assumption: if parameter is not referenced in function defintion body, it + // may indicate that it's outdated, so don't touch it. + if (!SourceParam->isReferenced()) + return false; + + // In case there is the primary template definition and (possibly several) + // template specializations (and each with possibly several redeclarations), + // it is not at all clear what to change. + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) + return false; + + // Other cases seem OK to allow replacements. + return true; +} + +DifferingParamsContainer +findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OtherDeclaration, + const FunctionDecl *OriginalDeclaration) { + DifferingParamsContainer DifferingParams; + + auto SourceParamIt = ParameterSourceDeclaration->param_begin(); + auto OtherParamIt = OtherDeclaration->param_begin(); + + while (SourceParamIt != ParameterSourceDeclaration->param_end() && + OtherParamIt != OtherDeclaration->param_end()) { + auto SourceParamName = (*SourceParamIt)->getName(); + auto OtherParamName = (*OtherParamIt)->getName(); + + // FIXME: Provide a way to extract commented out parameter name from comment + // next to it. + if (!SourceParamName.empty() && !OtherParamName.empty() && + SourceParamName != OtherParamName) { + SourceRange OtherParamNameRange = + DeclarationNameInfo((*OtherParamIt)->getDeclName(), + (*OtherParamIt)->getLocation()) + .getSourceRange(); + + bool GenerateFixItHint = checkIfFixItHintIsApplicable( + ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration); + + DifferingParams.emplace_back(SourceParamName, OtherParamName, + OtherParamNameRange, GenerateFixItHint); + } + + ++SourceParamIt; + ++OtherParamIt; + } + + return DifferingParams; +} + +InconsistentDeclarationsContainer +findInconsitentDeclarations(const FunctionDecl *OriginalDeclaration, + const FunctionDecl *ParameterSourceDeclaration, + SourceManager &SM) { + InconsistentDeclarationsContainer InconsistentDeclarations; + SourceLocation ParameterSourceLocation = + ParameterSourceDeclaration->getLocation(); + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + SourceLocation OtherLocation = OtherDeclaration->getLocation(); + if (OtherLocation != ParameterSourceLocation) { // Skip self. + DifferingParamsContainer DifferingParams = + findDifferingParamsInDeclaration(ParameterSourceDeclaration, + OtherDeclaration, + OriginalDeclaration); + if (!DifferingParams.empty()) { + InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(), + std::move(DifferingParams)); + } + } + } + + // Sort in order of appearance in translation unit to generate clear + // diagnostics. + std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(), + [&SM](const InconsistentDeclarationInfo &Info1, + const InconsistentDeclarationInfo &Info2) { + return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation, + Info2.DeclarationLocation); + }); + return InconsistentDeclarations; +} + +const FunctionDecl * +getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) { + const FunctionTemplateDecl *PrimaryTemplate = + OriginalDeclaration->getPrimaryTemplate(); + if (PrimaryTemplate != nullptr) { + // In case of template specializations, use primary template declaration as + // the source of parameter names. + return PrimaryTemplate->getTemplatedDecl(); + } + + // In other cases, try to change to function definition, if available. + + if (OriginalDeclaration->isThisDeclarationADefinition()) + return OriginalDeclaration; + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + if (OtherDeclaration->isThisDeclarationADefinition()) { + return OtherDeclaration; + } + } + + // No definition found, so return original declaration. + return OriginalDeclaration; +} + +std::string joinParameterNames( + const DifferingParamsContainer &DifferingParams, + llvm::function_ref ChooseParamName) { + llvm::SmallVector Buffer; + llvm::raw_svector_ostream Str(Buffer); + bool First = true; + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (First) + First = false; + else + Str << ", "; + + Str << "'" << ChooseParamName(ParamInfo).str() << "'"; + } + return Str.str().str(); +} + +void formatDifferingParamsDiagnostic( + InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, + StringRef OtherDeclarationDescription, + const DifferingParamsContainer &DifferingParams) { + auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) { + return ParamInfo.OtherName; + }; + auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) { + return ParamInfo.SourceName; + }; + + auto ParamDiag = + Check->diag(Location, + "differing parameters are named here: (%0), in %1: (%2)", + DiagnosticIDs::Level::Note) + << joinParameterNames(DifferingParams, ChooseOtherName) + << OtherDeclarationDescription + << joinParameterNames(DifferingParams, ChooseSourceName); + + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (ParamInfo.GenerateFixItHint) { + ParamDiag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), + ParamInfo.SourceName); + } + } +} + +void formatDiagnosticsForDeclarations( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations) { + Check->diag( + OriginalDeclaration->getLocation(), + "function %q0 has %1 other declaration%s1 with different parameter names") + << OriginalDeclaration + << static_cast(InconsistentDeclarations.size()); + int Count = 1; + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "the %ordinal0 inconsistent declaration seen here", + DiagnosticIDs::Level::Note) + << Count; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + "the other declaration", InconsistentDeclaration.DifferingParams); + + ++Count; + } +} + +void formatDiagnostics( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations, + StringRef FunctionDescription, StringRef ParameterSourceDescription) { + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "%0 %q1 has a %2 with different parameter names") + << FunctionDescription << OriginalDeclaration + << ParameterSourceDescription; + + Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", + DiagnosticIDs::Level::Note) + << ParameterSourceDescription; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + ParameterSourceDescription, InconsistentDeclaration.DifferingParams); + } +} + +} // anonymous namespace + +void InconsistentDeclarationParameterNameCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations()) + .bind("functionDecl"), + this); +} + +void InconsistentDeclarationParameterNameCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *OriginalDeclaration = + Result.Nodes.getNodeAs("functionDecl"); + + if (VisitedDeclarations.count(OriginalDeclaration) > 0) + return; // Avoid multiple warnings. + + const FunctionDecl *ParameterSourceDeclaration = + getParameterSourceDeclaration(OriginalDeclaration); + + InconsistentDeclarationsContainer InconsistentDeclarations = + findInconsitentDeclarations(OriginalDeclaration, + ParameterSourceDeclaration, + *Result.SourceManager); + if (InconsistentDeclarations.empty()) { + // Avoid unnecessary further visits. + markRedeclarationsAsVisited(OriginalDeclaration); + return; + } + + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, + "function template specialization", + "primary template declaration"); + } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, "function", "definition"); + } else { + formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, + OriginalDeclaration, + InconsistentDeclarations); + } + + markRedeclarationsAsVisited(OriginalDeclaration); +} + +void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( + const FunctionDecl *OriginalDeclaration) { + for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { + VisitedDeclarations.insert(Redecl); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h new file mode 100644 index 000000000..54860312e --- /dev/null +++ b/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h @@ -0,0 +1,45 @@ +//===- InconsistentDeclarationParameterNameCheck.h - clang-tidy-*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H + +#include "../ClangTidy.h" + +#include "llvm/ADT/DenseSet.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// \brief Checks for declarations of functions which differ in parameter names. +/// +/// For detailed documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.html +/// +class InconsistentDeclarationParameterNameCheck : public ClangTidyCheck { +public: + InconsistentDeclarationParameterNameCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void markRedeclarationsAsVisited(const FunctionDecl *FunctionDeclaration); + + llvm::DenseSet VisitedDeclarations; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H diff --git a/clang-tidy/readability/MisplacedArrayIndexCheck.cpp b/clang-tidy/readability/MisplacedArrayIndexCheck.cpp new file mode 100644 index 000000000..f5e09fabb --- /dev/null +++ b/clang-tidy/readability/MisplacedArrayIndexCheck.cpp @@ -0,0 +1,57 @@ +//===--- MisplacedArrayIndexCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MisplacedArrayIndexCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void MisplacedArrayIndexCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(arraySubscriptExpr(hasLHS(hasType(isInteger())), + hasRHS(hasType(isAnyPointer()))) + .bind("expr"), + this); +} + +void MisplacedArrayIndexCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ArraySubscriptE = + Result.Nodes.getNodeAs("expr"); + + auto Diag = diag(ArraySubscriptE->getLocStart(), "confusing array subscript " + "expression, usually the " + "index is inside the []"); + + // Only try to fixit when LHS and RHS can be swapped directly without changing + // the logic. + const Expr *RHSE = ArraySubscriptE->getRHS()->IgnoreParenImpCasts(); + if (!isa(RHSE) && !isa(RHSE) && + !isa(RHSE)) + return; + + const StringRef LText = tooling::fixit::getText( + ArraySubscriptE->getLHS()->getSourceRange(), *Result.Context); + const StringRef RText = tooling::fixit::getText( + ArraySubscriptE->getRHS()->getSourceRange(), *Result.Context); + + Diag << FixItHint::CreateReplacement( + ArraySubscriptE->getLHS()->getSourceRange(), RText); + Diag << FixItHint::CreateReplacement( + ArraySubscriptE->getRHS()->getSourceRange(), LText); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/MisplacedArrayIndexCheck.h b/clang-tidy/readability/MisplacedArrayIndexCheck.h new file mode 100644 index 000000000..e9a22314f --- /dev/null +++ b/clang-tidy/readability/MisplacedArrayIndexCheck.h @@ -0,0 +1,36 @@ +//===--- MisplacedArrayIndexCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Warn about unusual array index syntax (`index[array]` instead of +/// `array[index]`). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-misplaced-array-index.html +class MisplacedArrayIndexCheck : public ClangTidyCheck { +public: + MisplacedArrayIndexCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H diff --git a/clang-tidy/readability/NamedParameterCheck.cpp b/clang-tidy/readability/NamedParameterCheck.cpp new file mode 100644 index 000000000..ffdc813be --- /dev/null +++ b/clang-tidy/readability/NamedParameterCheck.cpp @@ -0,0 +1,127 @@ +//===--- NamedParameterCheck.cpp - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamedParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void NamedParameterCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("decl"), this); +} + +void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const auto *Function = Result.Nodes.getNodeAs("decl"); + SmallVector, 4> UnnamedParams; + + // Ignore implicitly generated members. + if (Function->isImplicit()) + return; + + // Ignore declarations without a definition if we're not dealing with an + // overriden method. + const FunctionDecl *Definition = nullptr; + if ((!Function->isDefined(Definition) || Function->isDefaulted() || + Function->isDeleted()) && + (!isa(Function) || + cast(Function)->size_overridden_methods() == 0)) + return; + + // TODO: Handle overloads. + // TODO: We could check that all redeclarations use the same name for + // arguments in the same position. + for (unsigned I = 0, E = Function->getNumParams(); I != E; ++I) { + const ParmVarDecl *Parm = Function->getParamDecl(I); + if (Parm->isImplicit()) + continue; + // Look for unnamed parameters. + if (!Parm->getName().empty()) + continue; + + // Don't warn on the dummy argument on post-inc and post-dec operators. + if ((Function->getOverloadedOperator() == OO_PlusPlus || + Function->getOverloadedOperator() == OO_MinusMinus) && + Parm->getType()->isSpecificBuiltinType(BuiltinType::Int)) + continue; + + // Sanity check the source locations. + if (!Parm->getLocation().isValid() || Parm->getLocation().isMacroID() || + !SM.isWrittenInSameFile(Parm->getLocStart(), Parm->getLocation())) + continue; + + // Skip gmock testing::Unused parameters. + if (auto Typedef = Parm->getType()->getAs()) + if (Typedef->getDecl()->getQualifiedNameAsString() == "testing::Unused") + continue; + + // Skip std::nullptr_t. + if (Parm->getType().getCanonicalType()->isNullPtrType()) + continue; + + // Look for comments. We explicitly want to allow idioms like + // void foo(int /*unused*/) + const char *Begin = SM.getCharacterData(Parm->getLocStart()); + const char *End = SM.getCharacterData(Parm->getLocation()); + StringRef Data(Begin, End - Begin); + if (Data.find("/*") != StringRef::npos) + continue; + + UnnamedParams.push_back(std::make_pair(Function, I)); + } + + // Emit only one warning per function but fixits for all unnamed parameters. + if (!UnnamedParams.empty()) { + const ParmVarDecl *FirstParm = + UnnamedParams.front().first->getParamDecl(UnnamedParams.front().second); + auto D = diag(FirstParm->getLocation(), + "all parameters should be named in a function"); + + for (auto P : UnnamedParams) { + // Fallback to an unused marker. + StringRef NewName = "unused"; + + // If the method is overridden, try to copy the name from the base method + // into the overrider. + const auto *M = dyn_cast(P.first); + if (M && M->size_overridden_methods() > 0) { + const ParmVarDecl *OtherParm = + (*M->begin_overridden_methods())->getParamDecl(P.second); + StringRef Name = OtherParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // If the definition has a named parameter use that name. + if (Definition) { + const ParmVarDecl *DefParm = Definition->getParamDecl(P.second); + StringRef Name = DefParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // Now insert the comment. Note that getLocation() points to the place + // where the name would be, this allows us to also get complex cases like + // function pointers right. + const ParmVarDecl *Parm = P.first->getParamDecl(P.second); + D << FixItHint::CreateInsertion(Parm->getLocation(), + " /*" + NewName.str() + "*/"); + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NamedParameterCheck.h b/clang-tidy/readability/NamedParameterCheck.h new file mode 100644 index 000000000..bd38ad2f5 --- /dev/null +++ b/clang-tidy/readability/NamedParameterCheck.h @@ -0,0 +1,42 @@ +//===--- NamedParameterCheck.h - clang-tidy ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find functions with unnamed arguments. +/// +/// The check implements the following rule originating in the Google C++ Style +/// Guide: +/// +/// https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions +/// +/// All parameters should be named, with identical names in the declaration and +/// implementation. +/// +/// Corresponding cpplint.py check name: 'readability/function'. +class NamedParameterCheck : public ClangTidyCheck { +public: + NamedParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H diff --git a/clang-tidy/readability/NamespaceCommentCheck.cpp b/clang-tidy/readability/NamespaceCommentCheck.cpp new file mode 100644 index 000000000..d9e711276 --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.cpp @@ -0,0 +1,147 @@ +//===--- NamespaceCommentCheck.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamespaceCommentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringExtras.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +NamespaceCommentCheck::NamespaceCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NamespaceCommentPattern("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" + "namespace( +([a-zA-Z0-9_]+))?\\.? *(\\*/)?$", + llvm::Regex::IgnoreCase), + ShortNamespaceLines(Options.get("ShortNamespaceLines", 1u)), + SpacesBeforeComments(Options.get("SpacesBeforeComments", 1u)) {} + +void NamespaceCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortNamespaceLines", ShortNamespaceLines); + Options.store(Opts, "SpacesBeforeComments", SpacesBeforeComments); +} + +void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (getLangOpts().CPlusPlus) + Finder->addMatcher(namespaceDecl().bind("namespace"), this); +} + +static bool locationsInSameFile(const SourceManager &Sources, + SourceLocation Loc1, SourceLocation Loc2) { + return Loc1.isFileID() && Loc2.isFileID() && + Sources.getFileID(Loc1) == Sources.getFileID(Loc2); +} + +static std::string getNamespaceComment(const NamespaceDecl *ND, + bool InsertLineBreak) { + std::string Fix = "// namespace"; + if (!ND->isAnonymousNamespace()) + Fix.append(" ").append(ND->getNameAsString()); + if (InsertLineBreak) + Fix.append("\n"); + return Fix; +} + +void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ND = Result.Nodes.getNodeAs("namespace"); + const SourceManager &Sources = *Result.SourceManager; + + if (!locationsInSameFile(Sources, ND->getLocStart(), ND->getRBraceLoc())) + return; + + // Don't require closing comments for namespaces spanning less than certain + // number of lines. + unsigned StartLine = Sources.getSpellingLineNumber(ND->getLocStart()); + unsigned EndLine = Sources.getSpellingLineNumber(ND->getRBraceLoc()); + if (EndLine - StartLine + 1 <= ShortNamespaceLines) + return; + + // Find next token after the namespace closing brace. + SourceLocation AfterRBrace = ND->getRBraceLoc().getLocWithOffset(1); + SourceLocation Loc = AfterRBrace; + Token Tok; + // Skip whitespace until we find the next token. + while (Lexer::getRawToken(Loc, Tok, Sources, getLangOpts()) || + Tok.is(tok::semi)) { + Loc = Loc.getLocWithOffset(1); + } + if (!locationsInSameFile(Sources, ND->getRBraceLoc(), Loc)) + return; + + bool NextTokenIsOnSameLine = Sources.getSpellingLineNumber(Loc) == EndLine; + // If we insert a line comment before the token in the same line, we need + // to insert a line break. + bool NeedLineBreak = NextTokenIsOnSameLine && Tok.isNot(tok::eof); + + SourceRange OldCommentRange(AfterRBrace, AfterRBrace); + std::string Message = "%0 not terminated with a closing comment"; + + // Try to find existing namespace closing comment on the same line. + if (Tok.is(tok::comment) && NextTokenIsOnSameLine) { + StringRef Comment(Sources.getCharacterData(Loc), Tok.getLength()); + SmallVector Groups; + if (NamespaceCommentPattern.match(Comment, &Groups)) { + StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; + StringRef Anonymous = Groups.size() > 3 ? Groups[3] : ""; + + // Check if the namespace in the comment is the same. + if ((ND->isAnonymousNamespace() && NamespaceNameInComment.empty()) || + (ND->getNameAsString() == NamespaceNameInComment && + Anonymous.empty())) { + // FIXME: Maybe we need a strict mode, where we always fix namespace + // comments with different format. + return; + } + + // Otherwise we need to fix the comment. + NeedLineBreak = Comment.startswith("/*"); + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = + (llvm::Twine( + "%0 ends with a comment that refers to a wrong namespace '") + + NamespaceNameInComment + "'") + .str(); + } else if (Comment.startswith("//")) { + // Assume that this is an unrecognized form of a namespace closing line + // comment. Replace it. + NeedLineBreak = false; + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = "%0 ends with an unrecognized comment"; + } + // If it's a block comment, just move it to the next line, as it can be + // multi-line or there may be other tokens behind it. + } + + std::string NamespaceName = + ND->isAnonymousNamespace() + ? "anonymous namespace" + : ("namespace '" + ND->getNameAsString() + "'"); + + diag(AfterRBrace, Message) + << NamespaceName << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OldCommentRange), + std::string(SpacesBeforeComments, ' ') + + getNamespaceComment(ND, NeedLineBreak)); + diag(ND->getLocation(), "%0 starts here", DiagnosticIDs::Note) + << NamespaceName; +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NamespaceCommentCheck.h b/clang-tidy/readability/NamespaceCommentCheck.h new file mode 100644 index 000000000..87d97d534 --- /dev/null +++ b/clang-tidy/readability/NamespaceCommentCheck.h @@ -0,0 +1,43 @@ +//===--- NamespaceCommentCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H + +#include "../ClangTidy.h" +#include "llvm/Support/Regex.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Checks that long namespaces have a closing comment. +/// +/// http://llvm.org/docs/CodingStandards.html#namespace-indentation +/// +/// https://google.github.io/styleguide/cppguide.html#Namespaces +class NamespaceCommentCheck : public ClangTidyCheck { +public: + NamespaceCommentCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + + llvm::Regex NamespaceCommentPattern; + const unsigned ShortNamespaceLines; + const unsigned SpacesBeforeComments; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H diff --git a/clang-tidy/readability/NonConstParameterCheck.cpp b/clang-tidy/readability/NonConstParameterCheck.cpp new file mode 100644 index 000000000..5ef9644d6 --- /dev/null +++ b/clang-tidy/readability/NonConstParameterCheck.cpp @@ -0,0 +1,214 @@ +//===--- NonConstParameterCheck.cpp - clang-tidy---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NonConstParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void NonConstParameterCheck::registerMatchers(MatchFinder *Finder) { + // Add parameters to Parameters. + Finder->addMatcher(parmVarDecl(unless(isInstantiated())).bind("Parm"), this); + + // C++ constructor. + Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this); + + // Track unused parameters, there is Wunused-parameter about unused + // parameters. + Finder->addMatcher(declRefExpr().bind("Ref"), this); + + // Analyse parameter usage in function. + Finder->addMatcher(stmt(anyOf(unaryOperator(anyOf(hasOperatorName("++"), + hasOperatorName("--"))), + binaryOperator(), callExpr(), returnStmt(), + cxxConstructExpr())) + .bind("Mark"), + this); + Finder->addMatcher(varDecl(hasInitializer(anything())).bind("Mark"), this); +} + +void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Parm = Result.Nodes.getNodeAs("Parm")) { + if (const DeclContext *D = Parm->getParentFunctionOrMethod()) { + if (const auto *M = dyn_cast(D)) { + if (M->isVirtual() || M->size_overridden_methods() != 0) + return; + } + } + addParm(Parm); + } else if (const auto *Ctor = + Result.Nodes.getNodeAs("Ctor")) { + for (const auto *Parm : Ctor->parameters()) + addParm(Parm); + for (const auto *Init : Ctor->inits()) + markCanNotBeConst(Init->getInit(), true); + } else if (const auto *Ref = Result.Nodes.getNodeAs("Ref")) { + setReferenced(Ref); + } else if (const auto *S = Result.Nodes.getNodeAs("Mark")) { + if (const auto *B = dyn_cast(S)) { + if (B->isAssignmentOp()) + markCanNotBeConst(B, false); + } else if (const auto *CE = dyn_cast(S)) { + // Typically, if a parameter is const then it is fine to make the data + // const. But sometimes the data is written even though the parameter + // is const. Mark all data passed by address to the function. + for (const auto *Arg : CE->arguments()) { + markCanNotBeConst(Arg->IgnoreParenCasts(), true); + } + + // Data passed by nonconst reference should not be made const. + if (const FunctionDecl *FD = CE->getDirectCallee()) { + unsigned ArgNr = 0U; + for (const auto *Par : FD->parameters()) { + if (ArgNr >= CE->getNumArgs()) + break; + const Expr *Arg = CE->getArg(ArgNr++); + // Is this a non constant reference parameter? + const Type *ParType = Par->getType().getTypePtr(); + if (!ParType->isReferenceType() || Par->getType().isConstQualified()) + continue; + markCanNotBeConst(Arg->IgnoreParenCasts(), false); + } + } + } else if (const auto *CE = dyn_cast(S)) { + for (const auto *Arg : CE->arguments()) { + markCanNotBeConst(Arg->IgnoreParenCasts(), true); + } + } else if (const auto *R = dyn_cast(S)) { + markCanNotBeConst(R->getRetValue(), true); + } else if (const auto *U = dyn_cast(S)) { + markCanNotBeConst(U, true); + } + } else if (const auto *VD = Result.Nodes.getNodeAs("Mark")) { + const QualType T = VD->getType(); + if ((T->isPointerType() && !T->getPointeeType().isConstQualified()) || + T->isArrayType()) + markCanNotBeConst(VD->getInit(), true); + } +} + +void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) { + // Only add nonconst integer/float pointer parameters. + const QualType T = Parm->getType(); + if (!T->isPointerType() || T->getPointeeType().isConstQualified() || + !(T->getPointeeType()->isIntegerType() || + T->getPointeeType()->isFloatingType())) + return; + + if (Parameters.find(Parm) != Parameters.end()) + return; + + ParmInfo PI; + PI.IsReferenced = false; + PI.CanBeConst = true; + Parameters[Parm] = PI; +} + +void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) { + auto It = Parameters.find(dyn_cast(Ref->getDecl())); + if (It != Parameters.end()) + It->second.IsReferenced = true; +} + +void NonConstParameterCheck::onEndOfTranslationUnit() { + diagnoseNonConstParameters(); +} + +void NonConstParameterCheck::diagnoseNonConstParameters() { + for (const auto &It : Parameters) { + const ParmVarDecl *Par = It.first; + const ParmInfo &ParamInfo = It.second; + + // Unused parameter => there are other warnings about this. + if (!ParamInfo.IsReferenced) + continue; + + // Parameter can't be const. + if (!ParamInfo.CanBeConst) + continue; + + diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const") + << Par->getName() + << FixItHint::CreateInsertion(Par->getLocStart(), "const "); + } +} + +void NonConstParameterCheck::markCanNotBeConst(const Expr *E, + bool CanNotBeConst) { + if (!E) + return; + + if (const auto *Cast = dyn_cast(E)) { + // If expression is const then ignore usage. + const QualType T = Cast->getType(); + if (T->isPointerType() && T->getPointeeType().isConstQualified()) + return; + } + + E = E->IgnoreParenCasts(); + + if (const auto *B = dyn_cast(E)) { + if (B->isAdditiveOp()) { + // p + 2 + markCanNotBeConst(B->getLHS(), CanNotBeConst); + markCanNotBeConst(B->getRHS(), CanNotBeConst); + } else if (B->isAssignmentOp()) { + markCanNotBeConst(B->getLHS(), false); + + // If LHS is not const then RHS can't be const. + const QualType T = B->getLHS()->getType(); + if (T->isPointerType() && !T->getPointeeType().isConstQualified()) + markCanNotBeConst(B->getRHS(), true); + } + } else if (const auto *C = dyn_cast(E)) { + markCanNotBeConst(C->getTrueExpr(), CanNotBeConst); + markCanNotBeConst(C->getFalseExpr(), CanNotBeConst); + } else if (const auto *U = dyn_cast(E)) { + if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec || + U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) { + if (const auto *SubU = + dyn_cast(U->getSubExpr()->IgnoreParenCasts())) + markCanNotBeConst(SubU->getSubExpr(), true); + markCanNotBeConst(U->getSubExpr(), CanNotBeConst); + } else if (U->getOpcode() == UO_Deref) { + if (!CanNotBeConst) + markCanNotBeConst(U->getSubExpr(), true); + } else { + markCanNotBeConst(U->getSubExpr(), CanNotBeConst); + } + } else if (const auto *A = dyn_cast(E)) { + markCanNotBeConst(A->getBase(), true); + } else if (const auto *CLE = dyn_cast(E)) { + markCanNotBeConst(CLE->getInitializer(), true); + } else if (const auto *Constr = dyn_cast(E)) { + for (const auto *Arg : Constr->arguments()) { + if (const auto *M = dyn_cast(Arg)) + markCanNotBeConst(cast(M->getTemporary()), CanNotBeConst); + } + } else if (const auto *ILE = dyn_cast(E)) { + for (unsigned I = 0U; I < ILE->getNumInits(); ++I) + markCanNotBeConst(ILE->getInit(I), true); + } else if (CanNotBeConst) { + // Referencing parameter. + if (const auto *D = dyn_cast(E)) { + auto It = Parameters.find(dyn_cast(D->getDecl())); + if (It != Parameters.end()) + It->second.CanBeConst = false; + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/NonConstParameterCheck.h b/clang-tidy/readability/NonConstParameterCheck.h new file mode 100644 index 000000000..3cc73e907 --- /dev/null +++ b/clang-tidy/readability/NonConstParameterCheck.h @@ -0,0 +1,64 @@ +//===--- NonConstParameterCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Warn when a pointer function parameter can be const. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-non-const-parameter.html +class NonConstParameterCheck : public ClangTidyCheck { +public: + NonConstParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + /// Parameter info. + struct ParmInfo { + /// Is function parameter referenced? + bool IsReferenced; + + /// Can function parameter be const? + bool CanBeConst; + }; + + /// Track all nonconst integer/float parameters. + std::map Parameters; + + /// Add function parameter. + void addParm(const ParmVarDecl *Parm); + + /// Set IsReferenced. + void setReferenced(const DeclRefExpr *Ref); + + /// Set CanNotBeConst. + /// Visits sub expressions recursively. If a DeclRefExpr is found + /// and CanNotBeConst is true the Parameter is marked as not-const. + /// The CanNotBeConst is updated as sub expressions are visited. + void markCanNotBeConst(const Expr *E, bool CanNotBeConst); + + /// Diagnose non const parameters. + void diagnoseNonConstParameters(); +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H diff --git a/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tidy/readability/ReadabilityTidyModule.cpp new file mode 100644 index 000000000..0455f6634 --- /dev/null +++ b/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -0,0 +1,104 @@ +//===--- ReadabilityTidyModule.cpp - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "AvoidConstParamsInDecls.h" +#include "BracesAroundStatementsCheck.h" +#include "ContainerSizeEmptyCheck.h" +#include "DeleteNullPointerCheck.h" +#include "DeletedDefaultCheck.h" +#include "ElseAfterReturnCheck.h" +#include "FunctionSizeCheck.h" +#include "IdentifierNamingCheck.h" +#include "ImplicitBoolCastCheck.h" +#include "InconsistentDeclarationParameterNameCheck.h" +#include "MisplacedArrayIndexCheck.h" +#include "NamedParameterCheck.h" +#include "NonConstParameterCheck.h" +#include "RedundantControlFlowCheck.h" +#include "RedundantDeclarationCheck.h" +#include "RedundantFunctionPtrDereferenceCheck.h" +#include "RedundantMemberInitCheck.h" +#include "RedundantSmartptrGetCheck.h" +#include "RedundantStringCStrCheck.h" +#include "RedundantStringInitCheck.h" +#include "SimplifyBooleanExprCheck.h" +#include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "UniqueptrDeleteReleaseCheck.h" + +namespace clang { +namespace tidy { +namespace readability { + +class ReadabilityModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "readability-avoid-const-params-in-decls"); + CheckFactories.registerCheck( + "readability-braces-around-statements"); + CheckFactories.registerCheck( + "readability-container-size-empty"); + CheckFactories.registerCheck( + "readability-delete-null-pointer"); + CheckFactories.registerCheck( + "readability-deleted-default"); + CheckFactories.registerCheck( + "readability-else-after-return"); + CheckFactories.registerCheck( + "readability-function-size"); + CheckFactories.registerCheck( + "readability-identifier-naming"); + CheckFactories.registerCheck( + "readability-implicit-bool-cast"); + CheckFactories.registerCheck( + "readability-inconsistent-declaration-parameter-name"); + CheckFactories.registerCheck( + "readability-misplaced-array-index"); + CheckFactories.registerCheck( + "readability-redundant-function-ptr-dereference"); + CheckFactories.registerCheck( + "readability-redundant-member-init"); + CheckFactories.registerCheck( + "readability-static-definition-in-anonymous-namespace"); + CheckFactories.registerCheck( + "readability-named-parameter"); + CheckFactories.registerCheck( + "readability-non-const-parameter"); + CheckFactories.registerCheck( + "readability-redundant-control-flow"); + CheckFactories.registerCheck( + "readability-redundant-declaration"); + CheckFactories.registerCheck( + "readability-redundant-smartptr-get"); + CheckFactories.registerCheck( + "readability-redundant-string-cstr"); + CheckFactories.registerCheck( + "readability-redundant-string-init"); + CheckFactories.registerCheck( + "readability-simplify-boolean-expr"); + CheckFactories.registerCheck( + "readability-uniqueptr-delete-release"); + } +}; + +// Register the ReadabilityModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add + X("readability-module", "Adds readability-related checks."); + +} // namespace readability + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ReadabilityModule. +volatile int ReadabilityModuleAnchorSource = 0; + +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantControlFlowCheck.cpp b/clang-tidy/readability/RedundantControlFlowCheck.cpp new file mode 100644 index 000000000..0788c42e7 --- /dev/null +++ b/clang-tidy/readability/RedundantControlFlowCheck.cpp @@ -0,0 +1,98 @@ +//===--- RedundantControlFlowCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantControlFlowCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +const char *const RedundantReturnDiag = "redundant return statement at the end " + "of a function with a void return type"; +const char *const RedundantContinueDiag = "redundant continue statement at the " + "end of loop statement"; + +bool isLocationInMacroExpansion(const SourceManager &SM, SourceLocation Loc) { + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +} // namespace + +void RedundantControlFlowCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl( + isDefinition(), returns(voidType()), + has(compoundStmt(hasAnySubstatement(returnStmt(unless(has(expr()))))) + .bind("return"))), + this); + auto CompoundContinue = + has(compoundStmt(hasAnySubstatement(continueStmt())).bind("continue")); + Finder->addMatcher( + stmt(anyOf(forStmt(), cxxForRangeStmt(), whileStmt(), doStmt()), + CompoundContinue), + this); +} + +void RedundantControlFlowCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Return = Result.Nodes.getNodeAs("return")) + checkRedundantReturn(Result, Return); + else if (const auto *Continue = + Result.Nodes.getNodeAs("continue")) + checkRedundantContinue(Result, Continue); +} + +void RedundantControlFlowCheck::checkRedundantReturn( + const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { + CompoundStmt::const_reverse_body_iterator last = Block->body_rbegin(); + if (const auto *Return = dyn_cast(*last)) + issueDiagnostic(Result, Block, Return->getSourceRange(), + RedundantReturnDiag); +} + +void RedundantControlFlowCheck::checkRedundantContinue( + const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { + CompoundStmt::const_reverse_body_iterator last = Block->body_rbegin(); + if (const auto *Continue = dyn_cast(*last)) + issueDiagnostic(Result, Block, Continue->getSourceRange(), + RedundantContinueDiag); +} + +void RedundantControlFlowCheck::issueDiagnostic( + const MatchFinder::MatchResult &Result, const CompoundStmt *const Block, + const SourceRange &StmtRange, const char *const Diag) { + SourceManager &SM = *Result.SourceManager; + if (isLocationInMacroExpansion(SM, StmtRange.getBegin())) + return; + + CompoundStmt::const_reverse_body_iterator Previous = ++Block->body_rbegin(); + SourceLocation Start; + if (Previous != Block->body_rend()) + Start = Lexer::findLocationAfterToken( + dyn_cast(*Previous)->getLocEnd(), tok::semi, SM, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true); + if (!Start.isValid()) + Start = StmtRange.getBegin(); + auto RemovedRange = CharSourceRange::getCharRange( + Start, Lexer::findLocationAfterToken( + StmtRange.getEnd(), tok::semi, SM, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + + diag(StmtRange.getBegin(), Diag) << FixItHint::CreateRemoval(RemovedRange); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantControlFlowCheck.h b/clang-tidy/readability/RedundantControlFlowCheck.h new file mode 100644 index 000000000..4b8b6fbf2 --- /dev/null +++ b/clang-tidy/readability/RedundantControlFlowCheck.h @@ -0,0 +1,51 @@ +//===--- RedundantControlFlowCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Eliminates redundant `return` statements at the end of a function that +/// returns `void`. +/// +/// Eliminates redundant `continue` statements at the end of a loop body. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-control-flow.html +class RedundantControlFlowCheck : public ClangTidyCheck { +public: + RedundantControlFlowCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void + checkRedundantReturn(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block); + + void + checkRedundantContinue(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block); + + void issueDiagnostic(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block, const SourceRange &StmtRange, + const char *Diag); +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H diff --git a/clang-tidy/readability/RedundantDeclarationCheck.cpp b/clang-tidy/readability/RedundantDeclarationCheck.cpp new file mode 100644 index 000000000..672cda655 --- /dev/null +++ b/clang-tidy/readability/RedundantDeclarationCheck.cpp @@ -0,0 +1,73 @@ +//===--- RedundantDeclarationCheck.cpp - clang-tidy------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantDeclarationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantDeclarationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(namedDecl(anyOf(varDecl(), functionDecl())).bind("Decl"), + this); +} + +void RedundantDeclarationCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs("Decl"); + const auto *Prev = D->getPreviousDecl(); + if (!Prev) + return; + if (!Prev->getLocation().isValid()) + return; + if (Prev->getLocation() == D->getLocation()) + return; + + const SourceManager &SM = *Result.SourceManager; + + const bool DifferentHeaders = + !SM.isInMainFile(D->getLocation()) && + !SM.isWrittenInSameFile(Prev->getLocation(), D->getLocation()); + + bool MultiVar = false; + if (const auto *VD = dyn_cast(D)) { + if (VD->getPreviousDecl()->getStorageClass() == SC_Extern && + VD->getStorageClass() != SC_Extern) + return; + // Is this a multivariable declaration? + for (const auto Other : VD->getDeclContext()->decls()) { + if (Other != D && Other->getLocStart() == VD->getLocStart()) { + MultiVar = true; + break; + } + } + } else { + const auto *FD = cast(D); + if (FD->isThisDeclarationADefinition()) + return; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + D->getSourceRange().getEnd(), 0, SM, Result.Context->getLangOpts()); + { + auto Diag = diag(D->getLocation(), "redundant %0 declaration") << D; + if (!MultiVar && !DifferentHeaders) + Diag << FixItHint::CreateRemoval( + SourceRange(D->getSourceRange().getBegin(), EndLoc)); + } + diag(Prev->getLocation(), "previously declared here", DiagnosticIDs::Note); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantDeclarationCheck.h b/clang-tidy/readability/RedundantDeclarationCheck.h new file mode 100644 index 000000000..96c483062 --- /dev/null +++ b/clang-tidy/readability/RedundantDeclarationCheck.h @@ -0,0 +1,35 @@ +//===--- RedundantDeclarationCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find redundant variable declarations. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-declaration.html +class RedundantDeclarationCheck : public ClangTidyCheck { +public: + RedundantDeclarationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H diff --git a/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp new file mode 100644 index 000000000..fa503c578 --- /dev/null +++ b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp @@ -0,0 +1,37 @@ +//===--- RedundantFunctionPtrDereferenceCheck.cpp - clang-tidy-------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantFunctionPtrDereferenceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantFunctionPtrDereferenceCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(unaryOperator(hasOperatorName("*"), + has(implicitCastExpr( + hasCastKind(CK_FunctionToPointerDecay)))) + .bind("op"), + this); +} + +void RedundantFunctionPtrDereferenceCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Operator = Result.Nodes.getNodeAs("op"); + diag(Operator->getOperatorLoc(), + "redundant repeated dereference of function pointer") + << FixItHint::CreateRemoval(Operator->getOperatorLoc()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h new file mode 100644 index 000000000..4cf6d1128 --- /dev/null +++ b/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h @@ -0,0 +1,35 @@ +//===--- RedundantFunctionPtrDereferenceCheck.h - clang-tidy-----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Eliminate redundant dereferences of a function pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-function-ptr-dereference.html +class RedundantFunctionPtrDereferenceCheck : public ClangTidyCheck { +public: + RedundantFunctionPtrDereferenceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H diff --git a/clang-tidy/readability/RedundantMemberInitCheck.cpp b/clang-tidy/readability/RedundantMemberInitCheck.cpp new file mode 100644 index 000000000..6d3650059 --- /dev/null +++ b/clang-tidy/readability/RedundantMemberInitCheck.cpp @@ -0,0 +1,68 @@ +//===--- RedundantMemberInitCheck.cpp - clang-tidy-------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantMemberInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantMemberInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + auto Construct = + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(hasParent( + cxxRecordDecl(unless(isTriviallyDefaultConstructible())))))) + .bind("construct"); + + Finder->addMatcher( + cxxConstructorDecl( + unless(isDelegatingConstructor()), + ofClass(unless( + anyOf(isUnion(), ast_matchers::isTemplateInstantiation()))), + forEachConstructorInitializer( + cxxCtorInitializer(isWritten(), + withInitializer(ignoringImplicit(Construct)), + unless(forField(hasType(isConstQualified())))) + .bind("init"))), + this); +} + +void RedundantMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Init = Result.Nodes.getNodeAs("init"); + const auto *Construct = Result.Nodes.getNodeAs("construct"); + + if (Construct->getNumArgs() == 0 || + Construct->getArg(0)->isDefaultArgument()) { + if (Init->isAnyMemberInitializer()) { + diag(Init->getSourceLocation(), "initializer for member %0 is redundant") + << Init->getMember() + << FixItHint::CreateRemoval(Init->getSourceRange()); + } else { + diag(Init->getSourceLocation(), + "initializer for base class %0 is redundant") + << Construct->getType() + << FixItHint::CreateRemoval(Init->getSourceRange()); + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantMemberInitCheck.h b/clang-tidy/readability/RedundantMemberInitCheck.h new file mode 100644 index 000000000..13cc9d3a9 --- /dev/null +++ b/clang-tidy/readability/RedundantMemberInitCheck.h @@ -0,0 +1,36 @@ +//===--- RedundantMemberInitCheck.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds member initializations that are unnecessary because the same default +/// constructor would be called if they were not present. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-redundant-member-init.html +class RedundantMemberInitCheck : public ClangTidyCheck { +public: + RedundantMemberInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.cpp b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp new file mode 100644 index 000000000..dd6866f5a --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.cpp @@ -0,0 +1,138 @@ +//===--- RedundantSmartptrGetCheck.cpp - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantSmartptrGetCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { +internal::Matcher callToGet(const internal::Matcher &OnClass) { + return cxxMemberCallExpr( + on(expr(anyOf(hasType(OnClass), + hasType(qualType( + pointsTo(decl(OnClass).bind("ptr_to_ptr")))))) + .bind("smart_pointer")), + unless(callee(memberExpr(hasObjectExpression(cxxThisExpr())))), + callee(cxxMethodDecl( + hasName("get"), + returns(qualType(pointsTo(type().bind("getType"))))))) + .bind("redundant_get"); +} + +void registerMatchersForGetArrowStart(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + const auto QuacksLikeASmartptr = recordDecl( + recordDecl().bind("duck_typing"), + has(cxxMethodDecl(hasName("operator->"), + returns(qualType(pointsTo(type().bind("op->Type")))))), + has(cxxMethodDecl(hasName("operator*"), returns(qualType(references( + type().bind("op*Type"))))))); + + // Catch 'ptr.get()->Foo()' + Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), + hasObjectExpression(ignoringImpCasts( + callToGet(QuacksLikeASmartptr)))), + Callback); + + // Catch '*ptr.get()' or '*ptr->get()' + Finder->addMatcher( + unaryOperator(hasOperatorName("*"), + hasUnaryOperand(callToGet(QuacksLikeASmartptr))), + Callback); +} + +void registerMatchersForGetEquals(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + // This one is harder to do with duck typing. + // The operator==/!= that we are looking for might be member or non-member, + // might be on global namespace or found by ADL, might be a template, etc. + // For now, lets keep a list of known standard types. + + const auto IsAKnownSmartptr = + recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); + + // Matches against nullptr. + Finder->addMatcher( + binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), + hasEitherOperand(ignoringImpCasts( + anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), + integerLiteral(equals(0))))), + hasEitherOperand(callToGet(IsAKnownSmartptr))), + Callback); + + // Matches against if(ptr.get()) + Finder->addMatcher( + ifStmt(hasCondition(ignoringImpCasts(callToGet(IsAKnownSmartptr)))), + Callback); + + // FIXME: Match and fix if (l.get() == r.get()). +} + +} // namespace + +void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + registerMatchersForGetArrowStart(Finder, this); + registerMatchersForGetEquals(Finder, this); +} + +namespace { +bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { + if (Result.Nodes.getNodeAs("duck_typing") == nullptr) + return true; + // Verify that the types match. + // We can't do this on the matcher because the type nodes can be different, + // even though they represent the same type. This difference comes from how + // the type is referenced (eg. through a typedef, a type trait, etc). + const Type *OpArrowType = + Result.Nodes.getNodeAs("op->Type")->getUnqualifiedDesugaredType(); + const Type *OpStarType = + Result.Nodes.getNodeAs("op*Type")->getUnqualifiedDesugaredType(); + const Type *GetType = + Result.Nodes.getNodeAs("getType")->getUnqualifiedDesugaredType(); + return OpArrowType == OpStarType && OpArrowType == GetType; +} +} // namespace + +void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { + if (!allReturnTypesMatch(Result)) + return; + + bool IsPtrToPtr = Result.Nodes.getNodeAs("ptr_to_ptr") != nullptr; + bool IsMemberExpr = Result.Nodes.getNodeAs("memberExpr") != nullptr; + const auto *GetCall = Result.Nodes.getNodeAs("redundant_get"); + const auto *Smartptr = Result.Nodes.getNodeAs("smart_pointer"); + + if (IsPtrToPtr && IsMemberExpr) { + // Ignore this case (eg. Foo->get()->DoSomething()); + return; + } + + StringRef SmartptrText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Smartptr->getSourceRange()), + *Result.SourceManager, getLangOpts()); + // Replace foo->get() with *foo, and foo.get() with foo. + std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); + diag(GetCall->getLocStart(), "redundant get() call on smart pointer") + << FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantSmartptrGetCheck.h b/clang-tidy/readability/RedundantSmartptrGetCheck.h new file mode 100644 index 000000000..1da619585 --- /dev/null +++ b/clang-tidy/readability/RedundantSmartptrGetCheck.h @@ -0,0 +1,40 @@ +//===--- RedundantSmartptrGetCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Find and remove redundant calls to smart pointer's `.get()` method. +/// +/// Examples: +/// +/// \code +/// ptr.get()->Foo() ==> ptr->Foo() +/// *ptr.get() ==> *ptr +/// *ptr->get() ==> **ptr +/// \endcode +class RedundantSmartptrGetCheck : public ClangTidyCheck { +public: + RedundantSmartptrGetCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H diff --git a/clang-tidy/readability/RedundantStringCStrCheck.cpp b/clang-tidy/readability/RedundantStringCStrCheck.cpp new file mode 100644 index 000000000..c6b384b3a --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.cpp @@ -0,0 +1,198 @@ +//===- RedundantStringCStrCheck.cpp - Check for redundant c_str calls -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements a check for redundant calls of c_str() on strings. +// +//===----------------------------------------------------------------------===// + +#include "RedundantStringCStrCheck.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +namespace { + +template +StringRef getText(const ast_matchers::MatchFinder::MatchResult &Result, + T const &Node) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(Node.getSourceRange()), + *Result.SourceManager, Result.Context->getLangOpts()); +} + +// Return true if expr needs to be put in parens when it is an argument of a +// prefix unary operator, e.g. when it is a binary or ternary operator +// syntactically. +bool needParensAfterUnaryOperator(const Expr &ExprNode) { + if (isa(&ExprNode) || + isa(&ExprNode)) { + return true; + } + if (const auto *Op = dyn_cast(&ExprNode)) { + return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && + Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + } + return false; +} + +// Format a pointer to an expression: prefix with '*' but simplify +// when it already begins with '&'. Return empty string on failure. +std::string +formatDereference(const ast_matchers::MatchFinder::MatchResult &Result, + const Expr &ExprNode) { + if (const auto *Op = dyn_cast(&ExprNode)) { + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&'. + return getText(Result, *Op->getSubExpr()->IgnoreParens()); + } + } + StringRef Text = getText(Result, ExprNode); + if (Text.empty()) + return std::string(); + // Add leading '*'. + if (needParensAfterUnaryOperator(ExprNode)) { + return (llvm::Twine("*(") + Text + ")").str(); + } + return (llvm::Twine("*") + Text).str(); +} + +} // end namespace + +void RedundantStringCStrCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Only register the matchers for C++; the functionality currently does not + // provide any benefit to other languages, despite being benign. + if (!getLangOpts().CPlusPlus) + return; + + // Match expressions of type 'string' or 'string*'. + const auto StringDecl = cxxRecordDecl(hasName("::std::basic_string")); + const auto StringExpr = + expr(anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl))))); + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + cxxConstructExpr( + argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + hasArgument(1, cxxDefaultArgExpr())))); + + // Match a call to the string 'c_str()' method. + const auto StringCStrCallExpr = + cxxMemberCallExpr(on(StringExpr.bind("arg")), + callee(memberExpr().bind("member")), + callee(cxxMethodDecl(hasAnyName("c_str", "data")))) + .bind("call"); + + // Detect redundant 'c_str()' calls through a string constructor. + Finder->addMatcher(cxxConstructExpr(StringConstructorExpr, + hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 's == str.c_str()' -> 's == str' + Finder->addMatcher( + cxxOperatorCallExpr( + anyOf( + hasOverloadedOperatorName("<"), hasOverloadedOperatorName(">"), + hasOverloadedOperatorName(">="), hasOverloadedOperatorName("<="), + hasOverloadedOperatorName("!="), hasOverloadedOperatorName("=="), + hasOverloadedOperatorName("+")), + anyOf(allOf(hasArgument(0, StringExpr), + hasArgument(1, StringCStrCallExpr)), + allOf(hasArgument(0, StringCStrCallExpr), + hasArgument(1, StringExpr)))), + this); + + // Detect: 'dst += str.c_str()' -> 'dst += str' + // Detect: 's = str.c_str()' -> 's = str' + Finder->addMatcher(cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("="), + hasOverloadedOperatorName("+=")), + hasArgument(0, StringExpr), + hasArgument(1, StringCStrCallExpr)), + this); + + // Detect: 'dst.append(str.c_str())' -> 'dst.append(str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), callee(decl(cxxMethodDecl(hasAnyName( + "append", "assign", "compare")))), + argumentCountIs(1), hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 'dst.compare(p, n, str.c_str())' -> 'dst.compare(p, n, str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasName("compare")))), + argumentCountIs(3), hasArgument(2, StringCStrCallExpr)), + this); + + // Detect: 'dst.find(str.c_str())' -> 'dst.find(str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasAnyName( + "find", "find_first_not_of", "find_first_of", + "find_last_not_of", "find_last_of", "rfind")))), + anyOf(argumentCountIs(1), argumentCountIs(2)), + hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 'dst.insert(pos, str.c_str())' -> 'dst.insert(pos, str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasName("insert")))), + argumentCountIs(2), hasArgument(1, StringCStrCallExpr)), + this); + + // Detect redundant 'c_str()' calls through a StringRef constructor. + Finder->addMatcher( + cxxConstructExpr( + // Implicit constructors of these classes are overloaded + // wrt. string types and they internally make a StringRef + // referring to the argument. Passing a string directly to + // them is preferred to passing a char pointer. + hasDeclaration(cxxMethodDecl(hasAnyName( + "::llvm::StringRef::StringRef", "::llvm::Twine::Twine"))), + argumentCountIs(1), + // The only argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). StringRef also has + // a constructor from string which is more efficient (avoids + // strlen), so we can construct StringRef from the string + // directly. + hasArgument(0, StringCStrCallExpr)), + this); +} + +void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *Arg = Result.Nodes.getNodeAs("arg"); + const auto *Member = Result.Nodes.getNodeAs("member"); + bool Arrow = Member->isArrow(); + // Replace the "call" node with the "arg" node, prefixed with '*' + // if the call was using '->' rather than '.'. + std::string ArgText = + Arrow ? formatDereference(Result, *Arg) : getText(Result, *Arg).str(); + if (ArgText.empty()) + return; + + diag(Call->getLocStart(), "redundant call to %0") + << Member->getMemberDecl() + << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantStringCStrCheck.h b/clang-tidy/readability/RedundantStringCStrCheck.h new file mode 100644 index 000000000..9406f8eab --- /dev/null +++ b/clang-tidy/readability/RedundantStringCStrCheck.h @@ -0,0 +1,32 @@ +//===--- RedundantStringCStrCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds unnecessary calls to `std::string::c_str()`. +class RedundantStringCStrCheck : public ClangTidyCheck { +public: + RedundantStringCStrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H diff --git a/clang-tidy/readability/RedundantStringInitCheck.cpp b/clang-tidy/readability/RedundantStringInitCheck.cpp new file mode 100644 index 000000000..b881e226e --- /dev/null +++ b/clang-tidy/readability/RedundantStringInitCheck.cpp @@ -0,0 +1,70 @@ +//===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RedundantStringInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void RedundantStringInitCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + cxxConstructExpr(argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + hasArgument(1, cxxDefaultArgExpr())))); + + // Match a string constructor expression with an empty string literal. + const auto EmptyStringCtorExpr = cxxConstructExpr( + StringConstructorExpr, + hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0))))); + + const auto EmptyStringCtorExprWithTemporaries = + cxxConstructExpr(StringConstructorExpr, + hasArgument(0, ignoringImplicit(EmptyStringCtorExpr))); + + // Match a variable declaration with an empty string literal as initializer. + // Examples: + // string foo = ""; + // string bar(""); + Finder->addMatcher( + namedDecl( + varDecl(hasType(cxxRecordDecl(hasName("basic_string"))), + hasInitializer(expr(ignoringImplicit(anyOf( + EmptyStringCtorExpr, + EmptyStringCtorExprWithTemporaries))) + .bind("expr"))), + unless(parmVarDecl())) + .bind("decl"), + this); +} + +void RedundantStringInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CtorExpr = Result.Nodes.getNodeAs("expr"); + const auto *Decl = Result.Nodes.getNodeAs("decl"); + diag(CtorExpr->getExprLoc(), "redundant string initialization") + << FixItHint::CreateReplacement(CtorExpr->getSourceRange(), + Decl->getName()); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/RedundantStringInitCheck.h b/clang-tidy/readability/RedundantStringInitCheck.h new file mode 100644 index 000000000..0a32eb6de --- /dev/null +++ b/clang-tidy/readability/RedundantStringInitCheck.h @@ -0,0 +1,32 @@ +//===- RedundantStringInitCheck.h - clang-tidy ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds unnecessary string initializations. +class RedundantStringInitCheck : public ClangTidyCheck { +public: + RedundantStringInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H diff --git a/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp new file mode 100644 index 000000000..be64335af --- /dev/null +++ b/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -0,0 +1,683 @@ +//===--- 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/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 RightExpressionId[] = "bool-op-expr-yields-expr"; +const char LeftExpressionId[] = "expr-op-bool-yields-expr"; +const char NegatedRightExpressionId[] = "bool-op-expr-yields-not-expr"; +const char NegatedLeftExpressionId[] = "expr-op-bool-yields-not-expr"; +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 LHSId[] = "lhs-expr"; +const char RHSId[] = "rhs-expr"; + +const char SimplifyOperatorDiagnostic[] = + "redundant boolean literal supplied to boolean operator"; +const char SimplifyConditionDiagnostic[] = + "redundant boolean literal in if statement condition"; +const char SimplifyConditionalReturnDiagnostic[] = + "redundant boolean literal in conditional return statement"; + +const CXXBoolLiteralExpr *getBoolLiteral(const MatchFinder::MatchResult &Result, + StringRef Id) { + const auto *Literal = Result.Nodes.getNodeAs(Id); + return (Literal && + Result.SourceManager->isMacroBodyExpansion(Literal->getLocStart())) + ? nullptr + : Literal; +} + +internal::Matcher returnsBool(bool Value, StringRef Id = "ignored") { + auto SimpleReturnsBool = + returnStmt(has(cxxBoolLiteral(equals(Value)).bind(Id))) + .bind("returns-bool"); + return anyOf(SimpleReturnsBool, + compoundStmt(statementCountIs(1), has(SimpleReturnsBool))); +} + +bool needsParensAfterUnaryNegation(const Expr *E) { + E = E->IgnoreImpCasts(); + if (isa(E) || isa(E)) + return true; + + if (const auto *Op = dyn_cast(E)) + return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + + return false; +} + +std::pair Opposites[] = { + {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; + +StringRef negatedOperator(const BinaryOperator *BinOp) { + const BinaryOperatorKind Opcode = BinOp->getOpcode(); + for (auto NegatableOp : Opposites) { + if (Opcode == NegatableOp.first) + return BinOp->getOpcodeStr(NegatableOp.second); + if (Opcode == NegatableOp.second) + return BinOp->getOpcodeStr(NegatableOp.first); + } + return StringRef(); +} + +std::pair OperatorNames[] = { + {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"}, + {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}}; + +StringRef getOperatorName(OverloadedOperatorKind OpKind) { + for (auto Name : OperatorNames) { + if (Name.first == OpKind) + return Name.second; + } + + return StringRef(); +} + +std::pair OppositeOverloads[] = + {{OO_EqualEqual, OO_ExclaimEqual}, + {OO_Less, OO_GreaterEqual}, + {OO_Greater, OO_LessEqual}}; + +StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) { + const OverloadedOperatorKind Opcode = OpCall->getOperator(); + for (auto NegatableOp : OppositeOverloads) { + if (Opcode == NegatableOp.first) + return getOperatorName(NegatableOp.second); + if (Opcode == NegatableOp.second) + return getOperatorName(NegatableOp.first); + } + return StringRef(); +} + +std::string asBool(StringRef text, bool NeedsStaticCast) { + if (NeedsStaticCast) + return ("static_cast(" + text + ")").str(); + + return text; +} + +bool needsNullPtrComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) + return ImpCast->getCastKind() == CK_PointerToBoolean || + ImpCast->getCastKind() == CK_MemberPointerToBoolean; + + return false; +} + +bool needsZeroComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) + return ImpCast->getCastKind() == CK_IntegralToBoolean; + + return false; +} + +bool needsStaticCast(const Expr *E) { + if (const auto *ImpCast = dyn_cast(E)) { + if (ImpCast->getCastKind() == CK_UserDefinedConversion && + ImpCast->getSubExpr()->getType()->isBooleanType()) { + if (const auto *MemCall = + dyn_cast(ImpCast->getSubExpr())) { + if (const auto *MemDecl = + dyn_cast(MemCall->getMethodDecl())) { + if (MemDecl->isExplicit()) + return true; + } + } + } + } + + E = E->IgnoreImpCasts(); + return !E->getType()->isBooleanType(); +} + +std::string compareExpressionToConstant(const MatchFinder::MatchResult &Result, + const Expr *E, bool Negated, + const char *Constant) { + E = E->IgnoreImpCasts(); + const std::string ExprText = + (isa(E) ? ("(" + getText(Result, *E) + ")") + : getText(Result, *E)) + .str(); + return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant; +} + +std::string compareExpressionToNullPtr(const MatchFinder::MatchResult &Result, + const Expr *E, bool Negated) { + const char *NullPtr = + Result.Context->getLangOpts().CPlusPlus11 ? "nullptr" : "NULL"; + return compareExpressionToConstant(Result, E, Negated, NullPtr); +} + +std::string compareExpressionToZero(const MatchFinder::MatchResult &Result, + const Expr *E, bool Negated) { + return compareExpressionToConstant(Result, E, Negated, "0"); +} + +std::string replacementExpression(const MatchFinder::MatchResult &Result, + bool Negated, const Expr *E) { + E = E->ignoreParenBaseCasts(); + const bool NeedsStaticCast = needsStaticCast(E); + if (Negated) { + if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), true); + + if (needsZeroComparison(UnOp->getSubExpr())) + return compareExpressionToZero(Result, UnOp->getSubExpr(), true); + + return replacementExpression(Result, false, UnOp->getSubExpr()); + } + } + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Result, E, false); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Result, E, false); + + StringRef NegatedOperator; + const Expr *LHS = nullptr; + const Expr *RHS = nullptr; + if (const auto *BinOp = dyn_cast(E)) { + NegatedOperator = negatedOperator(BinOp); + LHS = BinOp->getLHS(); + RHS = BinOp->getRHS(); + } else if (const auto *OpExpr = dyn_cast(E)) { + if (OpExpr->getNumArgs() == 2) { + NegatedOperator = negatedOperator(OpExpr); + LHS = OpExpr->getArg(0); + RHS = OpExpr->getArg(1); + } + } + if (!NegatedOperator.empty() && LHS && RHS) + return (asBool((getText(Result, *LHS) + " " + NegatedOperator + " " + + getText(Result, *RHS)) + .str(), + NeedsStaticCast)); + + StringRef Text = getText(Result, *E); + if (!NeedsStaticCast && needsParensAfterUnaryNegation(E)) + return ("!(" + Text + ")").str(); + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Result, E, false); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Result, E, false); + + return ("!" + asBool(Text, NeedsStaticCast)); + } + + if (const auto *UnOp = dyn_cast(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return compareExpressionToNullPtr(Result, UnOp->getSubExpr(), false); + + if (needsZeroComparison(UnOp->getSubExpr())) + return compareExpressionToZero(Result, UnOp->getSubExpr(), false); + } + } + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Result, E, true); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Result, E, true); + + return asBool(getText(Result, *E), NeedsStaticCast); +} + +const CXXBoolLiteralExpr *stmtReturnsBool(const ReturnStmt *Ret, bool Negated) { + if (const auto *Bool = dyn_cast(Ret->getRetValue())) { + if (Bool->getValue() == !Negated) + return Bool; + } + + return nullptr; +} + +const CXXBoolLiteralExpr *stmtReturnsBool(const IfStmt *IfRet, bool Negated) { + if (IfRet->getElse() != nullptr) + return nullptr; + + if (const auto *Ret = dyn_cast(IfRet->getThen())) + return stmtReturnsBool(Ret, Negated); + + if (const auto *Compound = dyn_cast(IfRet->getThen())) { + if (Compound->size() == 1) { + if (const auto *CompoundRet = dyn_cast(Compound->body_back())) + return stmtReturnsBool(CompoundRet, Negated); + } + } + + return nullptr; +} + +bool containsDiscardedTokens(const MatchFinder::MatchResult &Result, + CharSourceRange CharRange) { + std::string ReplacementText = + Lexer::getSourceText(CharRange, *Result.SourceManager, + Result.Context->getLangOpts()) + .str(); + Lexer Lex(CharRange.getBegin(), Result.Context->getLangOpts(), + ReplacementText.data(), ReplacementText.data(), + ReplacementText.data() + ReplacementText.size()); + Lex.SetCommentRetentionState(true); + + Token Tok; + while (!Lex.LexFromRawLexer(Tok)) { + if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash)) + return true; + } + + return false; +} + +} // namespace + +SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ChainedConditionalReturn(Options.get("ChainedConditionalReturn", 0U)), + ChainedConditionalAssignment( + Options.get("ChainedConditionalAssignment", 0U)) {} + +void SimplifyBooleanExprCheck::matchBoolBinOpExpr(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator( + isExpansionInMainFile(), hasOperatorName(OperatorName), + hasLHS(allOf(expr().bind(LHSId), + cxxBoolLiteral(equals(Value)).bind(BooleanId))), + hasRHS(expr().bind(RHSId)), + unless(hasRHS(hasDescendant(cxxBoolLiteral())))), + this); +} + +void SimplifyBooleanExprCheck::matchExprBinOpBool(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator( + isExpansionInMainFile(), hasOperatorName(OperatorName), + hasLHS(expr().bind(LHSId)), + unless( + hasLHS(anyOf(cxxBoolLiteral(), hasDescendant(cxxBoolLiteral())))), + hasRHS(allOf(expr().bind(RHSId), + cxxBoolLiteral(equals(Value)).bind(BooleanId)))), + this); +} + +void SimplifyBooleanExprCheck::matchBoolCompOpExpr(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator( + isExpansionInMainFile(), hasOperatorName(OperatorName), + hasLHS(allOf( + expr().bind(LHSId), + ignoringImpCasts(cxxBoolLiteral(equals(Value)).bind(BooleanId)))), + hasRHS(expr().bind(RHSId)), + unless(hasRHS(hasDescendant(cxxBoolLiteral())))), + this); +} + +void SimplifyBooleanExprCheck::matchExprCompOpBool(MatchFinder *Finder, + bool Value, + StringRef OperatorName, + StringRef BooleanId) { + Finder->addMatcher( + binaryOperator( + isExpansionInMainFile(), hasOperatorName(OperatorName), + unless(hasLHS(hasDescendant(cxxBoolLiteral()))), + hasLHS(expr().bind(LHSId)), + hasRHS(allOf(expr().bind(RHSId), + ignoringImpCasts( + cxxBoolLiteral(equals(Value)).bind(BooleanId))))), + this); +} + +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) { + matchBoolBinOpExpr(Finder, true, "&&", RightExpressionId); + matchBoolBinOpExpr(Finder, false, "||", RightExpressionId); + matchExprBinOpBool(Finder, false, "&&", RightExpressionId); + matchExprBinOpBool(Finder, true, "||", RightExpressionId); + matchBoolCompOpExpr(Finder, true, "==", RightExpressionId); + matchBoolCompOpExpr(Finder, false, "!=", RightExpressionId); + + matchExprBinOpBool(Finder, true, "&&", LeftExpressionId); + matchExprBinOpBool(Finder, false, "||", LeftExpressionId); + matchBoolBinOpExpr(Finder, false, "&&", LeftExpressionId); + matchBoolBinOpExpr(Finder, true, "||", LeftExpressionId); + matchExprCompOpBool(Finder, true, "==", LeftExpressionId); + matchExprCompOpBool(Finder, false, "!=", LeftExpressionId); + + matchBoolCompOpExpr(Finder, false, "==", NegatedRightExpressionId); + matchBoolCompOpExpr(Finder, true, "!=", NegatedRightExpressionId); + + matchExprCompOpBool(Finder, false, "==", NegatedLeftExpressionId); + matchExprCompOpBool(Finder, true, "!=", NegatedLeftExpressionId); + + 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 *LeftRemoved = + getBoolLiteral(Result, RightExpressionId)) + replaceWithExpression(Result, LeftRemoved, false); + else if (const CXXBoolLiteralExpr *RightRemoved = + getBoolLiteral(Result, LeftExpressionId)) + replaceWithExpression(Result, RightRemoved, true); + else if (const CXXBoolLiteralExpr *NegatedLeftRemoved = + getBoolLiteral(Result, NegatedRightExpressionId)) + replaceWithExpression(Result, NegatedLeftRemoved, false, true); + else if (const CXXBoolLiteralExpr *NegatedRightRemoved = + getBoolLiteral(Result, NegatedLeftExpressionId)) + replaceWithExpression(Result, NegatedRightRemoved, true, true); + else 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); +} + +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::replaceWithExpression( + const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, bool Negated) { + const auto *LHS = Result.Nodes.getNodeAs(LHSId); + const auto *RHS = Result.Nodes.getNodeAs(RHSId); + std::string Replacement = + replacementExpression(Result, Negated, UseLHS ? LHS : RHS); + SourceRange Range(LHS->getLocStart(), RHS->getLocEnd()); + issueDiag(Result, BoolLiteral->getLocStart(), SimplifyOperatorDiagnostic, + Range, 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..4a8076582 --- /dev/null +++ b/clang-tidy/readability/SimplifyBooleanExprCheck.h @@ -0,0 +1,102 @@ +//===--- 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: + void matchBoolBinOpExpr(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchExprBinOpBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchBoolCompOpExpr(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + void matchExprCompOpBool(ast_matchers::MatchFinder *Finder, bool Value, + StringRef OperatorName, StringRef BooleanId); + + 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 + replaceWithExpression(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral, bool UseLHS, + bool Negated = false); + + void + replaceWithThenStatement(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *BoolLiteral); + + void + replaceWithElseStatement(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXBoolLiteralExpr *FalseConditionRemoved); + + void + replaceWithCondition(const ast_matchers::MatchFinder::MatchResult &Result, + const ConditionalOperator *Ternary, + bool Negated = false); + + void replaceWithReturnCondition( + const ast_matchers::MatchFinder::MatchResult &Result, const IfStmt *If, + bool Negated = false); + + void + replaceWithAssignment(const ast_matchers::MatchFinder::MatchResult &Result, + const IfStmt *If, bool Negated = false); + + void replaceCompoundReturnWithCondition( + const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Compound, bool Negated = false); + + void issueDiag(const ast_matchers::MatchFinder::MatchResult &Result, + SourceLocation Loc, StringRef Description, + SourceRange ReplacementRange, StringRef Replacement); + + const bool ChainedConditionalReturn; + const bool ChainedConditionalAssignment; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H diff --git a/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp new file mode 100644 index 000000000..05546052f --- /dev/null +++ b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp @@ -0,0 +1,66 @@ +//===--- StaticDefinitionInAnonymousNamespaceCheck.cpp - clang-tidy--------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void StaticDefinitionInAnonymousNamespaceCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + namedDecl(anyOf(functionDecl(isDefinition(), isStaticStorageClass()), + varDecl(isDefinition(), isStaticStorageClass())), + hasParent(namespaceDecl(isAnonymous()))) + .bind("static-def"), + this); +} + +void StaticDefinitionInAnonymousNamespaceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Def = Result.Nodes.getNodeAs("static-def"); + // Skips all static definitions defined in Macro. + if (Def->getLocation().isMacroID()) + return; + + // Skips all static definitions in function scope. + const DeclContext *DC = Def->getDeclContext(); + if (DC->getDeclKind() != Decl::Namespace) + return; + + auto Diag = + diag(Def->getLocation(), "%0 is a static definition in " + "anonymous namespace; static is redundant here") + << Def; + Token Tok; + SourceLocation Loc = Def->getSourceRange().getBegin(); + while (Loc < Def->getSourceRange().getEnd() && + !Lexer::getRawToken(Loc, Tok, *Result.SourceManager, getLangOpts(), + true)) { + SourceRange TokenRange(Tok.getLocation(), Tok.getEndLoc()); + StringRef SourceText = + Lexer::getSourceText(CharSourceRange::getTokenRange(TokenRange), + *Result.SourceManager, getLangOpts()); + if (SourceText == "static") { + Diag << FixItHint::CreateRemoval(TokenRange); + break; + } + Loc = Tok.getEndLoc(); + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h new file mode 100644 index 000000000..03e99fd0e --- /dev/null +++ b/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h @@ -0,0 +1,36 @@ +//===--- StaticDefinitionInAnonymousNamespaceCheck.h - clang-tidy*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Finds static function and variable definitions in anonymous namespace. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.html +class StaticDefinitionInAnonymousNamespaceCheck : public ClangTidyCheck { +public: + StaticDefinitionInAnonymousNamespaceCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp new file mode 100644 index 000000000..3ad346cdd --- /dev/null +++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp @@ -0,0 +1,69 @@ +//===--- UniqueptrDeleteReleaseCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UniqueptrDeleteReleaseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +void UniqueptrDeleteReleaseCheck::registerMatchers(MatchFinder *Finder) { + auto IsSusbstituted = qualType(anyOf( + substTemplateTypeParmType(), hasDescendant(substTemplateTypeParmType()))); + + auto UniquePtrWithDefaultDelete = classTemplateSpecializationDecl( + hasName("std::unique_ptr"), + hasTemplateArgument(1, refersToType(qualType(hasDeclaration(cxxRecordDecl( + hasName("std::default_delete"))))))); + + Finder->addMatcher( + cxxDeleteExpr(has(ignoringParenImpCasts(cxxMemberCallExpr( + on(expr(hasType(UniquePtrWithDefaultDelete), + unless(hasType(IsSusbstituted))) + .bind("uptr")), + callee(cxxMethodDecl(hasName("release"))))))) + .bind("delete"), + this); +} + +void UniqueptrDeleteReleaseCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *PtrExpr = Result.Nodes.getNodeAs("uptr"); + const auto *DeleteExpr = Result.Nodes.getNodeAs("delete"); + + if (PtrExpr->getLocStart().isMacroID()) + return; + + // Ignore dependent types. + // It can give us false positives, so we go with false negatives instead to + // be safe. + if (PtrExpr->getType()->isDependentType()) + return; + + SourceLocation AfterPtr = Lexer::getLocForEndOfToken( + PtrExpr->getLocEnd(), 0, *Result.SourceManager, getLangOpts()); + + diag(DeleteExpr->getLocStart(), + "prefer '= nullptr' to 'delete x.release()' to reset unique_ptr<> " + "objects") + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + DeleteExpr->getLocStart(), PtrExpr->getLocStart())) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(AfterPtr, DeleteExpr->getLocEnd()), + " = nullptr"); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h new file mode 100644 index 000000000..fd86bdb9f --- /dev/null +++ b/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h @@ -0,0 +1,36 @@ +//===--- UniqueptrDeleteReleaseCheck.h - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// Flags statements of the form ``delete .release();`` and +/// replaces them with: `` = nullptr;`` +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-uniqueptr-delete-release.html +class UniqueptrDeleteReleaseCheck : public ClangTidyCheck { +public: + UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H diff --git a/clang-tidy/rename_check.py b/clang-tidy/rename_check.py new file mode 100755 index 000000000..e739a3878 --- /dev/null +++ b/clang-tidy/rename_check.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# +#===- rename_check.py - clang-tidy check renamer -------------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +import os +import glob +import argparse + + +def replaceInFile(fileName, sFrom, sTo): + if sFrom == sTo: + return + txt = None + with open(fileName, "r") as f: + txt = f.read() + + if sFrom not in txt: + return + + txt = txt.replace(sFrom, sTo) + print("Replace '%s' -> '%s' in '%s'" % (sFrom, sTo, fileName)) + with open(fileName, "w") as f: + f.write(txt) + + +def generateCommentLineHeader(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy ', + '-' * max(0, 42 - len(os.path.basename(filename))), + '*- C++ -*-===//']) + + +def generateCommentLineSource(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy', + '-' * max(0, 52 - len(os.path.basename(filename))), + '-===//']) + + +def fileRename(fileName, sFrom, sTo): + if sFrom not in fileName: + return fileName + newFileName = fileName.replace(sFrom, sTo) + print("Rename '%s' -> '%s'" % (fileName, newFileName)) + os.rename(fileName, newFileName) + return newFileName + + +def getListOfFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path, '*')) + for dirname in files: + if os.path.isdir(dirname): + files += glob.glob(os.path.join(dirname, '*')) + files += glob.glob(os.path.join(clang_tidy_path, '..', 'test', + 'clang-tidy', '*')) + files += glob.glob(os.path.join(clang_tidy_path, '..', 'docs', + 'clang-tidy', 'checks', '*')) + return [filename for filename in files if os.path.isfile(filename)] + + +def main(): + parser = argparse.ArgumentParser(description='Rename clang-tidy check.') + parser.add_argument('module', type=str, + help='Module where the renamed check is defined') + parser.add_argument('old_check_name', type=str, + help='Old check name.') + parser.add_argument('new_check_name', type=str, + help='New check name.') + args = parser.parse_args() + + args.module = args.module.lower() + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + args.old_check_name.split('-'))) + 'Check' + check_name_new_camel = (''.join(map(lambda elem: elem.capitalize(), + args.new_check_name.split('-'))) + + 'Check') + + clang_tidy_path = os.path.dirname(__file__) + + header_guard_old = (args.module.upper() + '_' + + args.old_check_name.upper().replace('-', '_')) + header_guard_new = (args.module.upper() + '_' + + args.new_check_name.upper().replace('-', '_')) + + for filename in getListOfFiles(clang_tidy_path): + originalName = filename + filename = fileRename(filename, args.old_check_name, + args.new_check_name) + filename = fileRename(filename, check_name_camel, check_name_new_camel) + replaceInFile(filename, generateCommentLineHeader(originalName), + generateCommentLineHeader(filename)) + replaceInFile(filename, generateCommentLineSource(originalName), + generateCommentLineSource(filename)) + replaceInFile(filename, header_guard_old, header_guard_new) + replaceInFile(filename, args.old_check_name, args.new_check_name) + replaceInFile(filename, check_name_camel, check_name_new_camel) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/tool/CMakeLists.txt b/clang-tidy/tool/CMakeLists.txt new file mode 100644 index 000000000..682d5eb21 --- /dev/null +++ b/clang-tidy/tool/CMakeLists.txt @@ -0,0 +1,33 @@ +set(LLVM_LINK_COMPONENTS + support + ) + +add_clang_executable(clang-tidy + ClangTidyMain.cpp + ) +add_dependencies(clang-tidy + clang-headers + ) +target_link_libraries(clang-tidy + clangAST + clangASTMatchers + clangBasic + clangTidy + clangTidyBoostModule + clangTidyCERTModule + clangTidyCppCoreGuidelinesModule + clangTidyGoogleModule + clangTidyLLVMModule + clangTidyMiscModule + clangTidyModernizeModule + clangTidyMPIModule + clangTidyPerformanceModule + clangTidyReadabilityModule + clangTooling + ) + +install(TARGETS clang-tidy + RUNTIME DESTINATION bin) + +install(PROGRAMS clang-tidy-diff.py DESTINATION share/clang) +install(PROGRAMS run-clang-tidy.py DESTINATION share/clang) diff --git a/clang-tidy/tool/ClangTidyMain.cpp b/clang-tidy/tool/ClangTidyMain.cpp new file mode 100644 index 000000000..7774ef215 --- /dev/null +++ b/clang-tidy/tool/ClangTidyMain.cpp @@ -0,0 +1,483 @@ +//===--- tools/extra/clang-tidy/ClangTidyMain.cpp - Clang tidy tool -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file This file implements a clang-tidy tool. +/// +/// This tool uses the Clang Tooling infrastructure, see +/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +/// for details on setting it up with LLVM source tree. +/// +//===----------------------------------------------------------------------===// + +#include "../ClangTidy.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "llvm/Support/Process.h" + +using namespace clang::ast_matchers; +using namespace clang::driver; +using namespace clang::tooling; +using namespace llvm; + +static cl::OptionCategory ClangTidyCategory("clang-tidy options"); + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::extrahelp ClangTidyHelp(R"( +Configuration files: + clang-tidy attempts to read configuration for each source file from a + .clang-tidy file located in the closest parent directory of the source + file. If any configuration options have a corresponding command-line + option, command-line option takes precedence. The effective + configuration can be inspected using -dump-config: + + $ clang-tidy -dump-config - -- + --- + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFilterRegex: '' + AnalyzeTemporaryDtors: false + 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("style", cl::desc(R"( +Fallback style for reformatting after inserting fixes +if there is no clang-format config file found. +)"), + cl::init("llvm"), + cl::cat(ClangTidyCategory)); + +static cl::opt ListChecks("list-checks", cl::desc(R"( +List all enabled checks and exit. Use with +-checks=* to list all available checks. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt ExplainConfig("explain-config", cl::desc(R"( +For each enabled check explains, where it is +enabled, i.e. in clang-tidy binary, command +line or a specific configuration file. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt Config("config", cl::desc(R"( +Specifies a configuration in YAML/JSON format: + -config="{Checks: '*', + CheckOptions: [{key: x, + value: y}]}" +When the value is empty, clang-tidy will +attempt to find a file named .clang-tidy for +each source file in its parent directories. +)"), + cl::init(""), cl::cat(ClangTidyCategory)); + +static cl::opt DumpConfig("dump-config", cl::desc(R"( +Dumps configuration in the YAML format to +stdout. This option can be used along with a +file name (and '--' if the file is outside of a +project with configured compilation database). +The configuration used for this file will be +printed. +Use along with -checks=* to include +configuration of all checks. +)"), + cl::init(false), cl::cat(ClangTidyCategory)); + +static cl::opt EnableCheckProfile("enable-check-profile", cl::desc(R"( +Enable per-check timing profiles, and print a +report to stderr. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +static cl::opt AnalyzeTemporaryDtors("analyze-temporary-dtors", + cl::desc(R"( +Enable temporary destructor-aware analysis in +clang-analyzer- checks. +This option overrides the value read from a +.clang-tidy file. +)"), + cl::init(false), + cl::cat(ClangTidyCategory)); + +static cl::opt ExportFixes("export-fixes", cl::desc(R"( +YAML file to store suggested fixes in. The +stored fixes can be applied to the input source +code with clang-apply-replacements. +)"), + cl::value_desc("filename"), + cl::cat(ClangTidyCategory)); + +namespace clang { +namespace tidy { + +static void printStats(const ClangTidyStats &Stats) { + if (Stats.errorsIgnored()) { + llvm::errs() << "Suppressed " << Stats.errorsIgnored() << " warnings ("; + StringRef Separator = ""; + if (Stats.ErrorsIgnoredNonUserCode) { + llvm::errs() << Stats.ErrorsIgnoredNonUserCode << " in non-user code"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredLineFilter) { + llvm::errs() << Separator << Stats.ErrorsIgnoredLineFilter + << " due to line filter"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredNOLINT) { + llvm::errs() << Separator << Stats.ErrorsIgnoredNOLINT << " NOLINT"; + Separator = ", "; + } + if (Stats.ErrorsIgnoredCheckFilter) + llvm::errs() << Separator << Stats.ErrorsIgnoredCheckFilter + << " with check filters"; + llvm::errs() << ").\n"; + if (Stats.ErrorsIgnoredNonUserCode) + llvm::errs() << "Use -header-filter=.* to display errors from all " + "non-system headers. Use -system-headers to display " + "errors from system headers as well.\n"; + } +} + +static void printProfileData(const ProfileData &Profile, + llvm::raw_ostream &OS) { + // Time is first to allow for sorting by it. + std::vector> Timers; + TimeRecord Total; + + for (const auto &P : Profile.Records) { + Timers.emplace_back(P.getValue(), P.getKey()); + Total += P.getValue(); + } + + std::sort(Timers.begin(), Timers.end()); + + std::string Line = "===" + std::string(73, '-') + "===\n"; + OS << Line; + + if (Total.getUserTime()) + OS << " ---User Time---"; + if (Total.getSystemTime()) + OS << " --System Time--"; + if (Total.getProcessTime()) + OS << " --User+System--"; + OS << " ---Wall Time---"; + if (Total.getMemUsed()) + OS << " ---Mem---"; + OS << " --- Name ---\n"; + + // Loop through all of the timing data, printing it out. + for (auto I = Timers.rbegin(), E = Timers.rend(); I != E; ++I) { + I->first.print(Total, OS); + OS << I->second << '\n'; + } + + Total.print(Total, OS); + OS << "Total\n"; + OS << Line << "\n"; + OS.flush(); +} + +static std::unique_ptr createOptionsProvider() { + ClangTidyGlobalOptions GlobalOptions; + if (std::error_code Err = parseLineFilter(LineFilter, GlobalOptions)) { + llvm::errs() << "Invalid LineFilter: " << Err.message() << "\n\nUsage:\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return nullptr; + } + + ClangTidyOptions DefaultOptions; + DefaultOptions.Checks = DefaultChecks; + DefaultOptions.WarningsAsErrors = ""; + DefaultOptions.HeaderFilterRegex = HeaderFilter; + DefaultOptions.SystemHeaders = SystemHeaders; + DefaultOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; + DefaultOptions.User = llvm::sys::Process::GetEnv("USER"); + // USERNAME is used on Windows. + if (!DefaultOptions.User) + DefaultOptions.User = llvm::sys::Process::GetEnv("USERNAME"); + + ClangTidyOptions OverrideOptions; + if (Checks.getNumOccurrences() > 0) + OverrideOptions.Checks = Checks; + if (WarningsAsErrors.getNumOccurrences() > 0) + OverrideOptions.WarningsAsErrors = WarningsAsErrors; + if (HeaderFilter.getNumOccurrences() > 0) + OverrideOptions.HeaderFilterRegex = HeaderFilter; + if (SystemHeaders.getNumOccurrences() > 0) + OverrideOptions.SystemHeaders = SystemHeaders; + if (AnalyzeTemporaryDtors.getNumOccurrences() > 0) + OverrideOptions.AnalyzeTemporaryDtors = AnalyzeTemporaryDtors; + + if (!Config.empty()) { + if (llvm::ErrorOr ParsedConfig = + parseConfiguration(Config)) { + return llvm::make_unique( + GlobalOptions, + ClangTidyOptions::getDefaults().mergeWith(DefaultOptions), + *ParsedConfig, OverrideOptions); + } else { + llvm::errs() << "Error: invalid configuration specified.\n" + << ParsedConfig.getError().message() << "\n"; + return nullptr; + } + } + return llvm::make_unique(GlobalOptions, DefaultOptions, + OverrideOptions); +} + +static int clangTidyMain(int argc, const char **argv) { + CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory, + cl::ZeroOrMore); + + auto OptionsProvider = createOptionsProvider(); + if (!OptionsProvider) + return 1; + + StringRef FileName("dummy"); + auto PathList = OptionsParser.getSourcePathList(); + if (!PathList.empty()) { + FileName = PathList.front(); + } + + SmallString<256> FilePath(FileName); + if (std::error_code EC = llvm::sys::fs::make_absolute(FilePath)) { + llvm::errs() << "Can't make absolute path from " << FileName << ": " + << EC.message() << "\n"; + } + ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath); + std::vector EnabledChecks = getCheckNames(EffectiveOptions); + + if (ExplainConfig) { + // FIXME: Show other ClangTidyOptions' fields, like ExtraArg. + std::vector + RawOptions = OptionsProvider->getRawOptions(FilePath); + for (const std::string &Check : EnabledChecks) { + for (auto It = RawOptions.rbegin(); It != RawOptions.rend(); ++It) { + if (It->first.Checks && GlobList(*It->first.Checks).contains(Check)) { + llvm::outs() << "'" << Check << "' is enabled in the " << It->second + << ".\n"; + break; + } + } + } + return 0; + } + + if (ListChecks) { + if (EnabledChecks.empty()) { + llvm::errs() << "No checks enabled.\n"; + return 1; + } + llvm::outs() << "Enabled checks:"; + for (const auto &CheckName : EnabledChecks) + llvm::outs() << "\n " << CheckName; + llvm::outs() << "\n\n"; + return 0; + } + + if (DumpConfig) { + EffectiveOptions.CheckOptions = getCheckOptions(EffectiveOptions); + llvm::outs() << configurationAsText( + ClangTidyOptions::getDefaults().mergeWith( + EffectiveOptions)) + << "\n"; + return 0; + } + + if (EnabledChecks.empty()) { + llvm::errs() << "Error: no checks enabled.\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return 1; + } + + if (PathList.empty()) { + llvm::errs() << "Error: no input files specified.\n"; + llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + return 1; + } + + ProfileData Profile; + + std::vector Errors; + ClangTidyStats Stats = + runClangTidy(std::move(OptionsProvider), OptionsParser.getCompilations(), + PathList, &Errors, EnableCheckProfile ? &Profile : nullptr); + bool FoundErrors = + std::find_if(Errors.begin(), Errors.end(), [](const ClangTidyError &E) { + return E.DiagLevel == ClangTidyError::Error; + }) != Errors.end(); + + const bool DisableFixes = Fix && FoundErrors && !FixErrors; + + unsigned WErrorCount = 0; + + // -fix-errors implies -fix. + handleErrors(Errors, (FixErrors || Fix) && !DisableFixes, FormatStyle, + WErrorCount); + + if (!ExportFixes.empty() && !Errors.empty()) { + std::error_code EC; + llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::F_None); + if (EC) { + llvm::errs() << "Error opening output file: " << EC.message() << '\n'; + return 1; + } + exportReplacements(FilePath.str(), Errors, OS); + } + + printStats(Stats); + if (DisableFixes) + llvm::errs() + << "Found compiler errors, but -fix-errors was not specified.\n" + "Fixes have NOT been applied.\n\n"; + + if (EnableCheckProfile) + printProfileData(Profile, llvm::errs()); + + if (WErrorCount) { + StringRef Plural = WErrorCount == 1 ? "" : "s"; + llvm::errs() << WErrorCount << " warning" << Plural << " treated as error" + << Plural << "\n"; + return WErrorCount; + } + + return 0; +} + +// This anchor is used to force the linker to link the CERTModule. +extern volatile int CERTModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CERTModuleAnchorDestination = + CERTModuleAnchorSource; + +// This anchor is used to force the linker to link the BoostModule. +extern volatile int BoostModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED BoostModuleAnchorDestination = + BoostModuleAnchorSource; + +// This anchor is used to force the linker to link the LLVMModule. +extern volatile int LLVMModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED LLVMModuleAnchorDestination = + LLVMModuleAnchorSource; + +// This anchor is used to force the linker to link the CppCoreGuidelinesModule. +extern volatile int CppCoreGuidelinesModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED CppCoreGuidelinesModuleAnchorDestination = + CppCoreGuidelinesModuleAnchorSource; + +// This anchor is used to force the linker to link the GoogleModule. +extern volatile int GoogleModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED GoogleModuleAnchorDestination = + GoogleModuleAnchorSource; + +// This anchor is used to force the linker to link the MiscModule. +extern volatile int MiscModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MiscModuleAnchorDestination = + MiscModuleAnchorSource; + +// This anchor is used to force the linker to link the ModernizeModule. +extern volatile int ModernizeModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ModernizeModuleAnchorDestination = + ModernizeModuleAnchorSource; + +// This anchor is used to force the linker to link the MPIModule. +extern volatile int MPIModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED MPIModuleAnchorDestination = + MPIModuleAnchorSource; + +// This anchor is used to force the linker to link the PerformanceModule. +extern volatile int PerformanceModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED PerformanceModuleAnchorDestination = + PerformanceModuleAnchorSource; + +// This anchor is used to force the linker to link the ReadabilityModule. +extern volatile int ReadabilityModuleAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED ReadabilityModuleAnchorDestination = + ReadabilityModuleAnchorSource; + +} // namespace tidy +} // namespace clang + +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..e3dcbe747 --- /dev/null +++ b/clang-tidy/tool/clang-tidy-diff.py @@ -0,0 +1,121 @@ +#!/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='') + 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) + command.extend(lines_by_file.keys()) + 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..e4dd3da44 --- /dev/null +++ b/clang-tidy/tool/run-clang-tidy.py @@ -0,0 +1,199 @@ +#!/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 +""" + +import argparse +import json +import multiprocessing +import os +import Queue +import re +import shutil +import subprocess +import sys +import tempfile +import threading + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print 'Error: could not find compilation database.' + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter): + """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) + start.append('-p=' + build_path) + start.append(f) + return start + + +def apply_fixes(args, tmpdir): + """Calls clang-apply-fixes on a given directory. Deletes the dir when done.""" + invocation = [args.clang_apply_replacements_binary] + if args.format: + invocation.append('-format') + invocation.append(tmpdir) + subprocess.call(invocation) + shutil.rmtree(tmpdir) + + +def run_tidy(args, tmpdir, build_path, queue): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, + tmpdir, build_path, args.header_filter) + sys.stdout.write(' '.join(invocation) + '\n') + subprocess.call(invocation) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' + 'in a compilation database. Requires ' + 'clang-tidy and clang-apply-replacements in ' + '$PATH.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy', + help='path to clang-tidy binary') + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', + default='clang-apply-replacements', + help='path to clang-apply-replacements binary') + parser.add_argument('-checks', default=None, + help='checks filter, when not specified, use clang-tidy ' + 'default') + parser.add_argument('-header-filter', default=None, + help='regular expression matching the names of the ' + 'headers to output diagnostics from. Diagnostics from ' + 'the main file of each translation unit are always ' + 'displayed.') + parser.add_argument('-j', type=int, default=0, + help='number of tidy instances to be run in parallel.') + parser.add_argument('files', nargs='*', default=['.*'], + help='files to be processed (regex on path)') + parser.add_argument('-fix', action='store_true', help='apply fix-its') + parser.add_argument('-format', action='store_true', help='Reformat code ' + 'after applying fixes') + parser.add_argument('-p', dest='build_path', + help='Path used to read a compile command database.') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + try: + invocation = [args.clang_tidy_binary, '-list-checks'] + invocation.append('-p=' + build_path) + if args.checks: + invocation.append('-checks=' + args.checks) + invocation.append('-') + print subprocess.check_output(invocation) + except: + print >>sys.stderr, "Unable to run clang-tidy." + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [entry['file'] for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + tmpdir = None + if args.fix: + tmpdir = tempfile.mkdtemp() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile('(' + ')|('.join(args.files) + ')') + + try: + # Spin up a bunch of tidy-launching threads. + queue = Queue.Queue(max_task) + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, build_path, queue)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + queue.put(name) + + # Wait for all threads to be done. + queue.join() + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print '\nCtrl-C detected, goodbye.' + if args.fix: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if args.fix: + print 'Applying fixes ...' + apply_fixes(args, tmpdir) + +if __name__ == '__main__': + main() diff --git a/clang-tidy/utils/ASTUtils.cpp b/clang-tidy/utils/ASTUtils.cpp new file mode 100644 index 000000000..cedac4f56 --- /dev/null +++ b/clang-tidy/utils/ASTUtils.cpp @@ -0,0 +1,28 @@ +//===---------- 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" + +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)); +} +} // 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..898ba9e64 --- /dev/null +++ b/clang-tidy/utils/ASTUtils.h @@ -0,0 +1,25 @@ +//===---------- 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); +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H diff --git a/clang-tidy/utils/CMakeLists.txt b/clang-tidy/utils/CMakeLists.txt new file mode 100644 index 000000000..9162bce1e --- /dev/null +++ b/clang-tidy/utils/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangTidyUtils + ASTUtils.cpp + DeclRefExprUtils.cpp + ExprSequence.cpp + FixItHintUtils.cpp + HeaderFileExtensionsUtils.cpp + HeaderGuard.cpp + IncludeInserter.cpp + IncludeSorter.cpp + LexerUtils.cpp + NamespaceAliaser.cpp + OptionsUtils.cpp + TypeTraits.cpp + UsingInserter.cpp + + LINK_LIBS + clangAST + clangASTMatchers + clangBasic + clangLex + clangTidy + ) diff --git a/clang-tidy/utils/DeclRefExprUtils.cpp b/clang-tidy/utils/DeclRefExprUtils.cpp new file mode 100644 index 000000000..a28b1d25a --- /dev/null +++ b/clang-tidy/utils/DeclRefExprUtils.cpp @@ -0,0 +1,171 @@ +//===--- 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("=")) + .bind("operatorCallExpr"))), + Decl, Context); + return !Matches.empty(); +} + +} // namespace decl_ref_expr +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/DeclRefExprUtils.h b/clang-tidy/utils/DeclRefExprUtils.h new file mode 100644 index 000000000..c25102f84 --- /dev/null +++ b/clang-tidy/utils/DeclRefExprUtils.h @@ -0,0 +1,66 @@ +//===--- DeclRefExprUtils.h - clang-tidy-------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_DECLREFEXPRUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_DECLREFEXPRUTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" +#include "llvm/ADT/SmallPtrSet.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace decl_ref_expr { + +/// \brief Returns true if all ``DeclRefExpr`` to the variable within ``Stmt`` +/// do not modify it. +/// +/// Returns ``true`` if only const methods or operators are called on the +/// variable or the variable is a const reference or value argument to a +/// ``callExpr()``. +bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, + ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Stmt``. +llvm::SmallPtrSet +allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Decl``. +llvm::SmallPtrSet +allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Stmt`` where +/// ``VarDecl`` is guaranteed to be accessed in a const fashion. +llvm::SmallPtrSet +constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, + ASTContext &Context); + +/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Decl`` where +/// ``VarDecl`` is guaranteed to be accessed in a const fashion. +llvm::SmallPtrSet +constReferenceDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, + ASTContext &Context); + +/// Returns ``true`` if ``DeclRefExpr`` is the argument of a copy-constructor +/// call expression within ``Decl``. +bool isCopyConstructorArgument(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context); + +/// Returns ``true`` if ``DeclRefExpr`` is the argument of a copy-assignment +/// operator CallExpr within ``Decl``. +bool isCopyAssignmentArgument(const DeclRefExpr &DeclRef, const Decl &Decl, + ASTContext &Context); + +} // namespace decl_ref_expr +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_DECLREFEXPRUTILS_H diff --git a/clang-tidy/utils/ExprSequence.cpp b/clang-tidy/utils/ExprSequence.cpp new file mode 100644 index 000000000..02d4a0bdd --- /dev/null +++ b/clang-tidy/utils/ExprSequence.cpp @@ -0,0 +1,182 @@ +//===---------- ExprSequence.cpp - clang-tidy -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ExprSequence.h" + +namespace clang { +namespace tidy { +namespace utils { + +// Returns the Stmt nodes that are parents of 'S', skipping any potential +// intermediate non-Stmt nodes. +// +// In almost all cases, this function returns a single parent or no parents at +// all. +// +// The case that a Stmt has multiple parents is rare but does actually occur in +// the parts of the AST that we're interested in. Specifically, InitListExpr +// nodes cause ASTContext::getParent() to return multiple parents for certain +// nodes in their subtree because RecursiveASTVisitor visits both the syntactic +// and semantic forms of InitListExpr, and the parent-child relationships are +// different between the two forms. +static SmallVector getParentStmts(const Stmt *S, + ASTContext *Context) { + SmallVector Result; + + ASTContext::DynTypedNodeList Parents = Context->getParents(*S); + + SmallVector NodesToProcess(Parents.begin(), + Parents.end()); + + while (!NodesToProcess.empty()) { + ast_type_traits::DynTypedNode Node = NodesToProcess.back(); + NodesToProcess.pop_back(); + + if (const auto *S = Node.get()) { + Result.push_back(S); + } else { + Parents = Context->getParents(Node); + NodesToProcess.append(Parents.begin(), Parents.end()); + } + } + + return Result; +} + +namespace { +bool isDescendantOrEqual(const Stmt *Descendant, const Stmt *Ancestor, + ASTContext *Context) { + if (Descendant == Ancestor) + return true; + for (const Stmt *Parent : getParentStmts(Descendant, Context)) { + if (isDescendantOrEqual(Parent, Ancestor, Context)) + return true; + } + + return false; +} +} + +ExprSequence::ExprSequence(const CFG *TheCFG, ASTContext *TheContext) + : Context(TheContext) { + for (const auto &SyntheticStmt : TheCFG->synthetic_stmts()) { + SyntheticStmtSourceMap[SyntheticStmt.first] = SyntheticStmt.second; + } +} + +bool ExprSequence::inSequence(const Stmt *Before, const Stmt *After) const { + Before = resolveSyntheticStmt(Before); + After = resolveSyntheticStmt(After); + + // If 'After' is in the subtree of the siblings that follow 'Before' in the + // chain of successors, we know that 'After' is sequenced after 'Before'. + for (const Stmt *Successor = getSequenceSuccessor(Before); Successor; + Successor = getSequenceSuccessor(Successor)) { + if (isDescendantOrEqual(After, Successor, Context)) + return true; + } + + // If 'After' is a parent of 'Before' or is sequenced after one of these + // parents, we know that it is sequenced after 'Before'. + for (const Stmt *Parent : getParentStmts(Before, Context)) { + if (Parent == After || inSequence(Parent, After)) + return true; + } + + return false; +} + +bool ExprSequence::potentiallyAfter(const Stmt *After, + const Stmt *Before) const { + return !inSequence(After, Before); +} + +const Stmt *ExprSequence::getSequenceSuccessor(const Stmt *S) const { + for (const Stmt *Parent : getParentStmts(S, Context)) { + if (const auto *BO = dyn_cast(Parent)) { + // Comma operator: Right-hand side is sequenced after the left-hand side. + if (BO->getLHS() == S && BO->getOpcode() == BO_Comma) + return BO->getRHS(); + } else if (const auto *InitList = dyn_cast(Parent)) { + // Initializer list: Each initializer clause is sequenced after the + // clauses that precede it. + for (unsigned I = 1; I < InitList->getNumInits(); ++I) { + if (InitList->getInit(I - 1) == S) + return InitList->getInit(I); + } + } else if (const auto *Compound = dyn_cast(Parent)) { + // Compound statement: Each sub-statement is sequenced after the + // statements that precede it. + const Stmt *Previous = nullptr; + for (const auto *Child : Compound->body()) { + if (Previous == S) + return Child; + Previous = Child; + } + } else if (const auto *TheDeclStmt = dyn_cast(Parent)) { + // Declaration: Every initializer expression is sequenced after the + // initializer expressions that precede it. + const Expr *PreviousInit = nullptr; + for (const Decl *TheDecl : TheDeclStmt->decls()) { + if (const auto *TheVarDecl = dyn_cast(TheDecl)) { + if (const Expr *Init = TheVarDecl->getInit()) { + if (PreviousInit == S) + return Init; + PreviousInit = Init; + } + } + } + } else if (const auto *ForRange = dyn_cast(Parent)) { + // Range-based for: Loop variable declaration is sequenced before the + // body. (We need this rule because these get placed in the same + // CFGBlock.) + if (S == ForRange->getLoopVarStmt()) + return ForRange->getBody(); + } else if (const auto *TheIfStmt = dyn_cast(Parent)) { + // If statement: If a variable is declared inside the condition, the + // expression used to initialize the variable is sequenced before the + // evaluation of the condition. + if (S == TheIfStmt->getConditionVariableDeclStmt()) + return TheIfStmt->getCond(); + } + } + + return nullptr; +} + +const Stmt *ExprSequence::resolveSyntheticStmt(const Stmt *S) const { + if (SyntheticStmtSourceMap.count(S)) + return SyntheticStmtSourceMap.lookup(S); + return S; +} + +StmtToBlockMap::StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext) + : Context(TheContext) { + for (const auto *B : *TheCFG) { + for (const auto &Elem : *B) { + if (Optional S = Elem.getAs()) + Map[S->getStmt()] = B; + } + } +} + +const CFGBlock *StmtToBlockMap::blockContainingStmt(const Stmt *S) const { + while (!Map.count(S)) { + SmallVector Parents = getParentStmts(S, Context); + if (Parents.empty()) + return nullptr; + S = Parents[0]; + } + + return Map.lookup(S); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/ExprSequence.h b/clang-tidy/utils/ExprSequence.h new file mode 100644 index 000000000..2b355d9a9 --- /dev/null +++ b/clang-tidy/utils/ExprSequence.h @@ -0,0 +1,124 @@ +//===------------- ExprSequence.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRSEQUENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRSEQUENCE_H + +#include "clang/Analysis/CFG.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// Provides information about the evaluation order of (sub-)expressions within +/// a `CFGBlock`. +/// +/// While a `CFGBlock` does contain individual `CFGElement`s for some +/// sub-expressions, the order in which those `CFGElement`s appear reflects +/// only one possible order in which the sub-expressions may be evaluated. +/// However, we want to warn if any of the potential evaluation orders can lead +/// to a use-after-move, not just the one contained in the `CFGBlock`. +/// +/// This class implements only a simplified version of the C++ sequencing +/// rules. The main limitation is that we do not distinguish between value +/// computation and side effect -- see the "Implementation" section for more +/// details. +/// +/// Note: `SequenceChecker` from SemaChecking.cpp does a similar job (and much +/// more thoroughly), but using it would require +/// - Pulling `SequenceChecker` out into a header file (i.e. making it part of +/// the API), +/// - Removing the dependency of `SequenceChecker` on `Sema`, and +/// - (Probably) modifying `SequenceChecker` to make it suitable to be used in +/// this context. +/// For the moment, it seems preferable to re-implement our own version of +/// sequence checking that is special-cased to what we need here. +/// +/// Implementation +/// -------------- +/// +/// `ExprSequence` uses two types of sequencing edges between nodes in the AST: +/// +/// - Every `Stmt` is assumed to be sequenced after its children. This is +/// overly optimistic because the standard only states that value computations +/// of operands are sequenced before the value computation of the operator, +/// making no guarantees about side effects (in general). +/// +/// For our purposes, this rule is sufficient, however, because this check is +/// interested in operations on objects, which are generally performed through +/// function calls (whether explicit and implicit). Function calls guarantee +/// that the value computations and side effects for all function arguments +/// are sequenced before the execution of the function. +/// +/// - In addition, some `Stmt`s are known to be sequenced before or after +/// their siblings. For example, the `Stmt`s that make up a `CompoundStmt`are +/// all sequenced relative to each other. The function +/// `getSequenceSuccessor()` implements these sequencing rules. +class ExprSequence { +public: + /// Initializes this `ExprSequence` with sequence information for the given + /// `CFG`. + ExprSequence(const CFG *TheCFG, ASTContext *TheContext); + + /// Returns whether \p Before is sequenced before \p After. + bool inSequence(const Stmt *Before, const Stmt *After) const; + + /// Returns whether \p After can potentially be evaluated after \p Before. + /// This is exactly equivalent to `!inSequence(After, Before)` but makes some + /// conditions read more naturally. + bool potentiallyAfter(const Stmt *After, const Stmt *Before) const; + +private: + // Returns the sibling of \p S (if any) that is directly sequenced after \p S, + // or nullptr if no such sibling exists. For example, if \p S is the child of + // a `CompoundStmt`, this would return the Stmt that directly follows \p S in + // the `CompoundStmt`. + // + // As the sequencing of many constructs that change control flow is already + // encoded in the `CFG`, this function only implements the sequencing rules + // for those constructs where sequencing cannot be inferred from the `CFG`. + const Stmt *getSequenceSuccessor(const Stmt *S) const; + + const Stmt *resolveSyntheticStmt(const Stmt *S) const; + + ASTContext *Context; + + llvm::DenseMap SyntheticStmtSourceMap; +}; + +/// Maps `Stmt`s to the `CFGBlock` that contains them. Some `Stmt`s may be +/// contained in more than one `CFGBlock`; in this case, they are mapped to the +/// innermost block (i.e. the one that is furthest from the root of the tree). +class StmtToBlockMap { +public: + /// Initializes the map for the given `CFG`. + StmtToBlockMap(const CFG *TheCFG, ASTContext *TheContext); + + /// Returns the block that \p S is contained in. Some `Stmt`s may be contained + /// in more than one `CFGBlock`; in this case, this function returns the + /// innermost block (i.e. the one that is furthest from the root of the tree). + const CFGBlock *blockContainingStmt(const Stmt *S) const; + +private: + ASTContext *Context; + + llvm::DenseMap Map; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRSEQUENCE_H diff --git a/clang-tidy/utils/FixItHintUtils.cpp b/clang-tidy/utils/FixItHintUtils.cpp new file mode 100644 index 000000000..d385cef22 --- /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::getPreviousNonCommentToken(Context, AmpLocation); + if (!Token.is(tok::unknown)) + AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0, + Context.getSourceManager(), + Context.getLangOpts()); + return FixItHint::CreateInsertion(AmpLocation, "&"); +} + +FixItHint changeVarDeclToConst(const VarDecl &Var) { + return FixItHint::CreateInsertion(Var.getTypeSpecStartLoc(), "const "); +} + +} // namespace fixit +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/FixItHintUtils.h b/clang-tidy/utils/FixItHintUtils.h new file mode 100644 index 000000000..e64a6e416 --- /dev/null +++ b/clang-tidy/utils/FixItHintUtils.h @@ -0,0 +1,32 @@ +//===--- FixItHintUtils.h - clang-tidy---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace fixit { + +/// \brief Creates fix to make ``VarDecl`` a reference by adding ``&``. +FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context); + +/// \brief Creates fix to make ``VarDecl`` const qualified. +FixItHint changeVarDeclToConst(const VarDecl &Var); + +} // namespace fixit +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H diff --git a/clang-tidy/utils/HeaderFileExtensionsUtils.cpp b/clang-tidy/utils/HeaderFileExtensionsUtils.cpp new file mode 100644 index 000000000..b734b8977 --- /dev/null +++ b/clang-tidy/utils/HeaderFileExtensionsUtils.cpp @@ -0,0 +1,71 @@ +//===--- HeaderFileExtensionsUtils.cpp - clang-tidy--------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "HeaderFileExtensionsUtils.h" +#include "clang/Basic/CharInfo.h" +#include "llvm/Support/Path.h" + +namespace clang { +namespace tidy { +namespace utils { + +bool isExpansionLocInHeaderFile( + SourceLocation Loc, const SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions) { + SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc); + return isHeaderFileExtension(SM.getFilename(ExpansionLoc), + HeaderFileExtensions); +} + +bool isPresumedLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions) { + PresumedLoc PresumedLocation = SM.getPresumedLoc(Loc); + return isHeaderFileExtension(PresumedLocation.getFilename(), + HeaderFileExtensions); +} + +bool isSpellingLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions) { + SourceLocation SpellingLoc = SM.getSpellingLoc(Loc); + return isHeaderFileExtension(SM.getFilename(SpellingLoc), + HeaderFileExtensions); +} + +bool parseHeaderFileExtensions(StringRef AllHeaderFileExtensions, + HeaderFileExtensionsSet &HeaderFileExtensions, + char delimiter) { + SmallVector Suffixes; + AllHeaderFileExtensions.split(Suffixes, delimiter); + HeaderFileExtensions.clear(); + for (StringRef Suffix : Suffixes) { + StringRef Extension = Suffix.trim(); + for (StringRef::const_iterator it = Extension.begin(); + it != Extension.end(); ++it) { + if (!isAlphanumeric(*it)) + return false; + } + HeaderFileExtensions.insert(Extension); + } + return true; +} + +bool isHeaderFileExtension( + StringRef FileName, const HeaderFileExtensionsSet &HeaderFileExtensions) { + StringRef extension = llvm::sys::path::extension(FileName); + if (extension.empty()) + return false; + // Skip "." prefix. + return HeaderFileExtensions.count(extension.substr(1)) > 0; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/HeaderFileExtensionsUtils.h b/clang-tidy/utils/HeaderFileExtensionsUtils.h new file mode 100644 index 000000000..c9fedf087 --- /dev/null +++ b/clang-tidy/utils/HeaderFileExtensionsUtils.h @@ -0,0 +1,52 @@ +//===--- HeaderFileExtensionsUtils.h - clang-tidy----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H + +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace tidy { +namespace utils { + +typedef llvm::SmallSet HeaderFileExtensionsSet; + +/// \brief Checks whether expansion location of \p Loc is in header file. +bool isExpansionLocInHeaderFile( + SourceLocation Loc, const SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +/// \brief Checks whether presumed location of \p Loc is in header file. +bool isPresumedLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +/// \brief Checks whether spelling location of \p Loc is in header file. +bool isSpellingLocInHeaderFile( + SourceLocation Loc, SourceManager &SM, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +/// \brief Parses header file extensions from a semicolon-separated list. +bool parseHeaderFileExtensions(StringRef AllHeaderFileExtensions, + HeaderFileExtensionsSet &HeaderFileExtensions, + char delimiter); + +/// \brief Decides whether a file has a header file extension. +bool isHeaderFileExtension(StringRef FileName, + const HeaderFileExtensionsSet &HeaderFileExtensions); + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADER_FILE_EXTENSIONS_UTILS_H diff --git a/clang-tidy/utils/HeaderGuard.cpp b/clang-tidy/utils/HeaderGuard.cpp new file mode 100644 index 000000000..acd63d3b0 --- /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 + // preceeded 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 is a header guard macro but it's not in the topmost position + // emit a plain warning without fix-its. This often happens when the guard + // macro is preceeded by includes. + // 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, + "Header guard after code/includes. Consider moving it up."); + SeenMacro = true; + break; + } + } + + if (SeenMacro) + continue; + + Check->diag(StartLoc, "header is missing header guard") + << FixItHint::CreateInsertion( + StartLoc, "#ifndef " + CPPVar + "\n#define " + CPPVar + "\n\n") + << FixItHint::CreateInsertion( + SM.getLocForEndOfFile(FID), + Check->shouldSuggestEndifComment(FileName) + ? "\n#" + Check->formatEndIf(CPPVar) + "\n" + : "\n#endif\n"); + } + } + +private: + std::vector> Macros; + llvm::StringMap Files; + std::map> + Ifndefs; + std::map EndIfs; + + Preprocessor *PP; + HeaderGuardCheck *Check; +}; +} // namespace + +void HeaderGuardCheck::registerPPCallbacks(CompilerInstance &Compiler) { + Compiler.getPreprocessor().addPPCallbacks( + llvm::make_unique(&Compiler.getPreprocessor(), + this)); +} + +bool HeaderGuardCheck::shouldSuggestEndifComment(StringRef FileName) { + return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); +} + +bool HeaderGuardCheck::shouldFixHeaderGuard(StringRef FileName) { return true; } + +bool HeaderGuardCheck::shouldSuggestToAddHeaderGuard(StringRef FileName) { + return utils::isHeaderFileExtension(FileName, HeaderFileExtensions); +} + +std::string HeaderGuardCheck::formatEndIf(StringRef HeaderGuard) { + return "endif // " + HeaderGuard.str(); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/HeaderGuard.h b/clang-tidy/utils/HeaderGuard.h new file mode 100644 index 000000000..c90a3127b --- /dev/null +++ b/clang-tidy/utils/HeaderGuard.h @@ -0,0 +1,64 @@ +//===--- HeaderGuard.h - clang-tidy -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H + +#include "../ClangTidy.h" +#include "../utils/HeaderFileExtensionsUtils.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// Finds and fixes header guards. +/// The check supports these options: +/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of +/// header files (The filename extension should not contain "." prefix). +/// ",h,hh,hpp,hxx" by default. +/// For extension-less header files, using an empty string or leaving an +/// empty string between "," if there are other filename extensions. +class HeaderGuardCheck : public ClangTidyCheck { +public: + HeaderGuardCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + RawStringHeaderFileExtensions( + Options.getLocalOrGlobal("HeaderFileExtensions", ",h,hh,hpp,hxx")) { + utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions, + HeaderFileExtensions, ','); + } + void registerPPCallbacks(CompilerInstance &Compiler) override; + + /// Returns ``true`` if the check should suggest inserting a trailing comment + /// on the ``#endif`` of the header guard. It will use the same name as + /// returned by ``HeaderGuardCheck::getHeaderGuard``. + virtual bool shouldSuggestEndifComment(StringRef Filename); + /// Returns ``true`` if the check should suggest changing an existing header + /// guard to the string returned by ``HeaderGuardCheck::getHeaderGuard``. + virtual bool shouldFixHeaderGuard(StringRef Filename); + /// Returns ``true`` if the check should add a header guard to the file + /// if it has none. + virtual bool shouldSuggestToAddHeaderGuard(StringRef Filename); + /// Returns a replacement for the ``#endif`` line with a comment mentioning + /// \p HeaderGuard. The replacement should start with ``endif``. + virtual std::string formatEndIf(StringRef HeaderGuard); + /// Gets the canonical header guard for a file. + virtual std::string getHeaderGuard(StringRef Filename, + StringRef OldGuard = StringRef()) = 0; + +private: + std::string RawStringHeaderFileExtensions; + utils::HeaderFileExtensionsSet HeaderFileExtensions; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_HEADERGUARD_H diff --git a/clang-tidy/utils/IncludeInserter.cpp b/clang-tidy/utils/IncludeInserter.cpp new file mode 100644 index 000000000..9fc7f5213 --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.cpp @@ -0,0 +1,85 @@ +//===-------- IncludeInserter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeInserter.h" +#include "clang/Lex/Token.h" + +namespace clang { +namespace tidy { +namespace utils { + +class IncludeInserterCallback : public PPCallbacks { +public: + explicit IncludeInserterCallback(IncludeInserter *Inserter) + : Inserter(Inserter) {} + // Implements PPCallbacks::InclusionDerective(). Records the names and source + // locations of the inclusions in the main source file being processed. + void InclusionDirective(SourceLocation HashLocation, + const Token &IncludeToken, StringRef FileNameRef, + bool IsAngled, CharSourceRange FileNameRange, + const FileEntry * /*IncludedFile*/, + StringRef /*SearchPath*/, StringRef /*RelativePath*/, + const Module * /*ImportedModule*/) override { + Inserter->AddInclude(FileNameRef, IsAngled, HashLocation, + IncludeToken.getEndLoc()); + } + +private: + IncludeInserter *Inserter; +}; + +IncludeInserter::IncludeInserter(const SourceManager &SourceMgr, + const LangOptions &LangOpts, + IncludeSorter::IncludeStyle Style) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style) {} + +IncludeInserter::~IncludeInserter() {} + +std::unique_ptr IncludeInserter::CreatePPCallbacks() { + return llvm::make_unique(this); +} + +llvm::Optional +IncludeInserter::CreateIncludeInsertion(FileID FileID, StringRef Header, + bool IsAngled) { + // We assume the same Header will never be included both angled and not + // angled. + if (!InsertedHeaders[FileID].insert(Header).second) + return llvm::None; + + if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) { + // This may happen if there have been no preprocessor directives in this + // file. + IncludeSorterByFile.insert(std::make_pair( + FileID, + llvm::make_unique( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(SourceMgr.getLocForStartOfFile(FileID)), + Style))); + } + return IncludeSorterByFile[FileID]->CreateIncludeInsertion(Header, IsAngled); +} + +void IncludeInserter::AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, + SourceLocation EndLocation) { + FileID FileID = SourceMgr.getFileID(HashLocation); + if (IncludeSorterByFile.find(FileID) == IncludeSorterByFile.end()) { + IncludeSorterByFile.insert(std::make_pair( + FileID, llvm::make_unique( + &SourceMgr, &LangOpts, FileID, + SourceMgr.getFilename(HashLocation), Style))); + } + IncludeSorterByFile[FileID]->AddInclude(FileName, IsAngled, HashLocation, + EndLocation); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/IncludeInserter.h b/clang-tidy/utils/IncludeInserter.h new file mode 100644 index 000000000..75f2554b3 --- /dev/null +++ b/clang-tidy/utils/IncludeInserter.h @@ -0,0 +1,84 @@ +//===---------- IncludeInserter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H + +#include "IncludeSorter.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/PPCallbacks.h" +#include +#include + +namespace clang { +namespace tidy { +namespace utils { + +/// \brief Produces fixes to insert specified includes to source files, if not +/// yet present. +/// +/// ``IncludeInserter`` can be used by ``ClangTidyCheck`` in the following +/// fashion: +/// \code +/// class MyCheck : public ClangTidyCheck { +/// public: +/// void registerPPCallbacks(CompilerInstance& Compiler) override { +/// Inserter.reset(new IncludeInserter(&Compiler.getSourceManager(), +/// &Compiler.getLangOpts())); +/// Compiler.getPreprocessor().addPPCallbacks( +/// Inserter->CreatePPCallback()); +/// } +/// +/// void registerMatchers(ast_matchers::MatchFinder* Finder) override { ... } +/// +/// void check( +/// const ast_matchers::MatchFinder::MatchResult& Result) override { +/// ... +/// Inserter->CreateIncludeInsertion( +/// Result.SourceManager->getMainFileID(), "path/to/Header.h", +/// /*IsAngled=*/false); +/// ... +/// } +/// +/// private: +/// std::unique_ptr Inserter; +/// }; +/// \endcode +class IncludeInserter { +public: + IncludeInserter(const SourceManager &SourceMgr, const LangOptions &LangOpts, + IncludeSorter::IncludeStyle Style); + ~IncludeInserter(); + + /// Create ``PPCallbacks`` for registration with the compiler's preprocessor. + std::unique_ptr CreatePPCallbacks(); + + /// Creates a \p Header inclusion directive fixit. Returns ``llvm::None`` on + /// error or if inclusion directive already exists. + llvm::Optional + CreateIncludeInsertion(FileID FileID, llvm::StringRef Header, bool IsAngled); + +private: + void AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, SourceLocation EndLocation); + + llvm::DenseMap> IncludeSorterByFile; + llvm::DenseMap> InsertedHeaders; + const SourceManager &SourceMgr; + const LangOptions &LangOpts; + const IncludeSorter::IncludeStyle Style; + friend class IncludeInserterCallback; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDEINSERTER_H diff --git a/clang-tidy/utils/IncludeSorter.cpp b/clang-tidy/utils/IncludeSorter.cpp new file mode 100644 index 000000000..1502da761 --- /dev/null +++ b/clang-tidy/utils/IncludeSorter.cpp @@ -0,0 +1,290 @@ +//===---------- IncludeSorter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "IncludeSorter.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { + +namespace { + +StringRef RemoveFirstSuffix(StringRef Str, ArrayRef Suffixes) { + for (StringRef Suffix : Suffixes) { + if (Str.endswith(Suffix)) { + return Str.substr(0, Str.size() - Suffix.size()); + } + } + return Str; +} + +StringRef MakeCanonicalName(StringRef Str, IncludeSorter::IncludeStyle Style) { + // The list of suffixes to remove from source file names to get the + // "canonical" file names. + // E.g. tools/sort_includes.cc and tools/sort_includes_test.cc + // would both canonicalize to tools/sort_includes and tools/sort_includes.h + // (once canonicalized) will match as being the main include file associated + // with the source files. + if (Style == IncludeSorter::IS_LLVM) { + return RemoveFirstSuffix( + RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), {"Test"}); + } + return RemoveFirstSuffix( + RemoveFirstSuffix(Str, {".cc", ".cpp", ".c", ".h", ".hpp"}), + {"_unittest", "_regtest", "_test"}); +} + +// Scan to the end of the line and return the offset of the next line. +size_t FindNextLine(const char *Text) { + size_t EOLIndex = std::strcspn(Text, "\n"); + return Text[EOLIndex] == '\0' ? EOLIndex : EOLIndex + 1; +} + +IncludeSorter::IncludeKinds +DetermineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile, + bool IsAngled, IncludeSorter::IncludeStyle Style) { + // Compute the two "canonical" forms of the include's filename sans extension. + // The first form is the include's filename without ".h" or "-inl.h" at the + // end. The second form is the first form with "/public/" in the file path + // replaced by "/internal/". + if (IsAngled) { + // If the system include () ends with ".h", then it is a normal C-style + // include. Otherwise assume it is a C++-style extensionless include. + return IncludeFile.endswith(".h") ? IncludeSorter::IK_CSystemInclude + : IncludeSorter::IK_CXXSystemInclude; + } + StringRef CanonicalInclude = MakeCanonicalName(IncludeFile, Style); + if (CanonicalFile.endswith(CanonicalInclude) + || CanonicalInclude.endswith(CanonicalFile)) { + return IncludeSorter::IK_MainTUInclude; + } + if (Style == IncludeSorter::IS_Google) { + std::pair Parts = CanonicalInclude.split("/public/"); + std::string AltCanonicalInclude = + Parts.first.str() + "/internal/" + Parts.second.str(); + std::string ProtoCanonicalInclude = + Parts.first.str() + "/proto/" + Parts.second.str(); + + // Determine the kind of this inclusion. + if (CanonicalFile.equals(AltCanonicalInclude) || + CanonicalFile.equals(ProtoCanonicalInclude)) { + return IncludeSorter::IK_MainTUInclude; + } + } + return IncludeSorter::IK_NonSystemInclude; +} + +} // namespace + +IncludeSorter::IncludeSorter(const SourceManager *SourceMgr, + const LangOptions *LangOpts, const FileID FileID, + StringRef FileName, IncludeStyle Style) + : SourceMgr(SourceMgr), LangOpts(LangOpts), Style(Style), + CurrentFileID(FileID), CanonicalFile(MakeCanonicalName(FileName, Style)) { +} + +void IncludeSorter::AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, + SourceLocation EndLocation) { + int Offset = FindNextLine(SourceMgr->getCharacterData(EndLocation)); + + // Record the relevant location information for this inclusion directive. + IncludeLocations[FileName].push_back( + SourceRange(HashLocation, EndLocation.getLocWithOffset(Offset))); + SourceLocations.push_back(IncludeLocations[FileName].back()); + + // Stop if this inclusion is a duplicate. + if (IncludeLocations[FileName].size() > 1) + return; + + // Add the included file's name to the appropriate bucket. + IncludeKinds Kind = + DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style); + if (Kind != IK_InvalidInclude) + IncludeBucket[Kind].push_back(FileName.str()); +} + +Optional IncludeSorter::CreateIncludeInsertion(StringRef FileName, + bool IsAngled) { + std::string IncludeStmt = + IsAngled ? llvm::Twine("#include <" + FileName + ">\n").str() + : llvm::Twine("#include \"" + FileName + "\"\n").str(); + if (SourceLocations.empty()) { + // If there are no includes in this file, add it in the first line. + // FIXME: insert after the file comment or the header guard, if present. + IncludeStmt.append("\n"); + return FixItHint::CreateInsertion( + SourceMgr->getLocForStartOfFile(CurrentFileID), IncludeStmt); + } + + auto IncludeKind = + DetermineIncludeKind(CanonicalFile, FileName, IsAngled, Style); + + if (!IncludeBucket[IncludeKind].empty()) { + for (const std::string &IncludeEntry : IncludeBucket[IncludeKind]) { + if (FileName < IncludeEntry) { + const auto &Location = IncludeLocations[IncludeEntry][0]; + return FixItHint::CreateInsertion(Location.getBegin(), IncludeStmt); + } else if (FileName == IncludeEntry) { + return llvm::None; + } + } + // FileName comes after all include entries in bucket, insert it after + // last. + const std::string &LastInclude = IncludeBucket[IncludeKind].back(); + SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back(); + return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(), + IncludeStmt); + } + // Find the non-empty include bucket to be sorted directly above + // 'IncludeKind'. If such a bucket exists, we'll want to sort the include + // after that bucket. If no such bucket exists, find the first non-empty + // include bucket in the file. In that case, we'll want to sort the include + // before that bucket. + IncludeKinds NonEmptyKind = IK_InvalidInclude; + for (int i = IK_InvalidInclude - 1; i >= 0; --i) { + if (!IncludeBucket[i].empty()) { + NonEmptyKind = static_cast(i); + if (NonEmptyKind < IncludeKind) + break; + } + } + if (NonEmptyKind == IK_InvalidInclude) { + return llvm::None; + } + + if (NonEmptyKind < IncludeKind) { + // Create a block after. + const std::string &LastInclude = IncludeBucket[NonEmptyKind].back(); + SourceRange LastIncludeLocation = IncludeLocations[LastInclude].back(); + IncludeStmt = '\n' + IncludeStmt; + return FixItHint::CreateInsertion(LastIncludeLocation.getEnd(), + IncludeStmt); + } + // Create a block before. + const std::string &FirstInclude = IncludeBucket[NonEmptyKind][0]; + SourceRange FirstIncludeLocation = IncludeLocations[FirstInclude].back(); + IncludeStmt.append("\n"); + return FixItHint::CreateInsertion(FirstIncludeLocation.getBegin(), + IncludeStmt); +} + +std::vector IncludeSorter::GetEdits() { + if (SourceLocations.empty()) + return {}; + + typedef std::map> + FileLineToSourceEditMap; + FileLineToSourceEditMap Edits; + auto SourceLocationIterator = SourceLocations.begin(); + auto SourceLocationIteratorEnd = SourceLocations.end(); + + // Compute the Edits that need to be done to each line to add, replace, or + // delete inclusions. + for (int IncludeKind = 0; IncludeKind < IK_InvalidInclude; ++IncludeKind) { + std::sort(IncludeBucket[IncludeKind].begin(), + IncludeBucket[IncludeKind].end()); + for (const auto &IncludeEntry : IncludeBucket[IncludeKind]) { + auto &Location = IncludeLocations[IncludeEntry]; + SourceRangeVector::iterator LocationIterator = Location.begin(); + SourceRangeVector::iterator LocationIteratorEnd = Location.end(); + SourceRange FirstLocation = *LocationIterator; + + // If the first occurrence of a particular include is on the current + // source line we are examining, leave it alone. + if (FirstLocation == *SourceLocationIterator) + ++LocationIterator; + + // Add the deletion Edits for any (remaining) instances of this inclusion, + // and remove their Locations from the source Locations to be processed. + for (; LocationIterator != LocationIteratorEnd; ++LocationIterator) { + int LineNumber = + SourceMgr->getSpellingLineNumber(LocationIterator->getBegin()); + Edits[LineNumber] = std::make_pair(*LocationIterator, ""); + SourceLocationIteratorEnd = + std::remove(SourceLocationIterator, SourceLocationIteratorEnd, + *LocationIterator); + } + + if (FirstLocation == *SourceLocationIterator) { + // Do nothing except move to the next source Location (Location of an + // inclusion in the original, unchanged source file). + ++SourceLocationIterator; + continue; + } + + // Add (or append to) the replacement text for this line in source file. + int LineNumber = + SourceMgr->getSpellingLineNumber(SourceLocationIterator->getBegin()); + if (Edits.find(LineNumber) == Edits.end()) { + Edits[LineNumber].first = + SourceRange(SourceLocationIterator->getBegin()); + } + StringRef SourceText = Lexer::getSourceText( + CharSourceRange::getCharRange(FirstLocation), *SourceMgr, *LangOpts); + Edits[LineNumber].second.append(SourceText.data(), SourceText.size()); + } + + // Clear the bucket. + IncludeBucket[IncludeKind].clear(); + } + + // Go through the single-line Edits and combine them into blocks of Edits. + int CurrentEndLine = 0; + SourceRange CurrentRange; + std::string CurrentText; + std::vector Fixes; + for (const auto &LineEdit : Edits) { + // If the current edit is on the next line after the previous edit, add it + // to the current block edit. + if (LineEdit.first == CurrentEndLine + 1 && + CurrentRange.getBegin() != CurrentRange.getEnd()) { + SourceRange EditRange = LineEdit.second.first; + if (EditRange.getBegin() != EditRange.getEnd()) { + ++CurrentEndLine; + CurrentRange.setEnd(EditRange.getEnd()); + } + CurrentText += LineEdit.second.second; + // Otherwise report the current block edit and start a new block. + } else { + if (CurrentEndLine) { + Fixes.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(CurrentRange), CurrentText)); + } + + CurrentEndLine = LineEdit.first; + CurrentRange = LineEdit.second.first; + CurrentText = LineEdit.second.second; + } + } + // Finally, report the current block edit if there is one. + if (CurrentEndLine) { + Fixes.push_back(FixItHint::CreateReplacement( + CharSourceRange::getCharRange(CurrentRange), CurrentText)); + } + + // Reset the remaining internal state. + SourceLocations.clear(); + IncludeLocations.clear(); + return Fixes; +} + +IncludeSorter::IncludeStyle +IncludeSorter::parseIncludeStyle(const std::string &Value) { + return Value == "llvm" ? IS_LLVM : IS_Google; +} + +StringRef IncludeSorter::toString(IncludeStyle Style) { + return Style == IS_LLVM ? "llvm" : "google"; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/IncludeSorter.h b/clang-tidy/utils/IncludeSorter.h new file mode 100644 index 000000000..07fa293dc --- /dev/null +++ b/clang-tidy/utils/IncludeSorter.h @@ -0,0 +1,86 @@ +//===------------ IncludeSorter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H + +#include "../ClangTidy.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +/// Class used by ``IncludeInserterCallback`` to record the names of the +/// inclusions in a given source file being processed and generate the necessary +/// commands to sort the inclusions according to the precedence encoded in +/// ``IncludeKinds``. +class IncludeSorter { +public: + /// Supported include styles. + enum IncludeStyle { IS_LLVM = 0, IS_Google = 1 }; + + /// Converts "llvm" to ``IS_LLVM``, otherwise returns ``IS_Google``. + static IncludeStyle parseIncludeStyle(const std::string &Value); + + /// Converts ``IncludeStyle`` to string representation. + static StringRef toString(IncludeStyle Style); + + /// The classifications of inclusions, in the order they should be sorted. + enum IncludeKinds { + IK_MainTUInclude = 0, ///< e.g. ``#include "foo.h"`` when editing foo.cc + IK_CSystemInclude = 1, ///< e.g. ``#include `` + IK_CXXSystemInclude = 2, ///< e.g. ``#include `` + IK_NonSystemInclude = 3, ///< e.g. ``#include "bar.h"`` + IK_InvalidInclude = 4 ///< total number of valid ``IncludeKind``s + }; + + /// ``IncludeSorter`` constructor; takes the FileID and name of the file to be + /// processed by the sorter. + IncludeSorter(const SourceManager *SourceMgr, const LangOptions *LangOpts, + const FileID FileID, StringRef FileName, IncludeStyle Style); + + /// Returns the ``SourceManager``-specific file ID for the file being handled + /// by the sorter. + const FileID current_FileID() const { return CurrentFileID; } + + /// Adds the given include directive to the sorter. + void AddInclude(StringRef FileName, bool IsAngled, + SourceLocation HashLocation, SourceLocation EndLocation); + + /// Returns the edits needed to sort the current set of includes and reset the + /// internal state (so that different blocks of includes are sorted separately + /// within the same file). + std::vector GetEdits(); + + /// Creates a quoted inclusion directive in the right sort order. Returns None + /// on error or if header inclusion directive for header already exists. + Optional CreateIncludeInsertion(StringRef FileName, bool IsAngled); + +private: + typedef SmallVector SourceRangeVector; + + const SourceManager *SourceMgr; + const LangOptions *LangOpts; + const IncludeStyle Style; + FileID CurrentFileID; + /// The file name stripped of common suffixes. + StringRef CanonicalFile; + /// Locations of visited include directives. + SourceRangeVector SourceLocations; + /// Mapping from file name to #include locations. + llvm::StringMap IncludeLocations; + /// Includes sorted into buckets. + SmallVector IncludeBucket[IK_InvalidInclude]; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_INCLUDESORTER_H diff --git a/clang-tidy/utils/LexerUtils.cpp b/clang-tidy/utils/LexerUtils.cpp new file mode 100644 index 000000000..f80661d68 --- /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 getPreviousNonCommentToken(const ASTContext &Context, + SourceLocation Location) { + 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()) && + !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..f2185927a --- /dev/null +++ b/clang-tidy/utils/LexerUtils.h @@ -0,0 +1,31 @@ +//===--- 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 non-comment token skipping over any comment text or +/// ``tok::unknown`` if not found. +Token getPreviousNonCommentToken(const ASTContext &Context, + SourceLocation Location); + +} // namespace lexer +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_LEXER_UTILS_H diff --git a/clang-tidy/utils/Matchers.h b/clang-tidy/utils/Matchers.h new file mode 100644 index 000000000..adafdd9a7 --- /dev/null +++ b/clang-tidy/utils/Matchers.h @@ -0,0 +1,51 @@ +//===--- Matchers.h - clang-tidy-------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H + +#include "TypeTraits.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +namespace clang { +namespace tidy { +namespace matchers { + +AST_MATCHER(BinaryOperator, isRelationalOperator) { + return Node.isRelationalOp(); +} + +AST_MATCHER(BinaryOperator, isEqualityOperator) { return Node.isEqualityOp(); } + +AST_MATCHER(BinaryOperator, isComparisonOperator) { + return Node.isComparisonOp(); +} + +AST_MATCHER(QualType, isExpensiveToCopy) { + llvm::Optional IsExpensive = + utils::type_traits::isExpensiveToCopy(Node, Finder->getASTContext()); + return IsExpensive && *IsExpensive; +} + +AST_MATCHER(RecordDecl, isTriviallyDefaultConstructible) { + return utils::type_traits::recordIsTriviallyDefaultConstructible( + Node, Finder->getASTContext()); +} + +// Returns QualType matcher for references to const. +AST_MATCHER_FUNCTION(ast_matchers::TypeMatcher, isReferenceToConst) { + using namespace ast_matchers; + return referenceType(pointee(qualType(isConstQualified()))); +} + +} // namespace matchers +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H diff --git a/clang-tidy/utils/NamespaceAliaser.cpp b/clang-tidy/utils/NamespaceAliaser.cpp new file mode 100644 index 000000000..1a5120deb --- /dev/null +++ b/clang-tidy/utils/NamespaceAliaser.cpp @@ -0,0 +1,97 @@ +//===---------- NamespaceAliaser.cpp - clang-tidy -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "NamespaceAliaser.h" + +#include "ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +namespace clang { +namespace tidy { +namespace utils { + +using namespace ast_matchers; + +NamespaceAliaser::NamespaceAliaser(const SourceManager &SourceMgr) + : SourceMgr(SourceMgr) {} + +AST_MATCHER_P(NamespaceAliasDecl, hasTargetNamespace, + ast_matchers::internal::Matcher, innerMatcher) { + return innerMatcher.matches(*Node.getNamespace(), Finder, Builder); +} + +Optional +NamespaceAliaser::createAlias(ASTContext &Context, const Stmt &Statement, + StringRef Namespace, + const std::vector &Abbreviations) { + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (!Function || !Function->hasBody()) + return None; + + if (AddedAliases[Function].count(Namespace.str()) != 0) + return None; + + // FIXME: Doesn't consider the order of declarations. + // If we accidentially pick an alias defined later in the function, + // the output won't compile. + // FIXME: Also doesn't consider file or class-scope aliases. + + const auto *ExistingAlias = selectFirst( + "alias", + match(functionDecl(hasBody(compoundStmt(has(declStmt( + has(namespaceAliasDecl(hasTargetNamespace(hasName(Namespace))) + .bind("alias"))))))), + *Function, Context)); + + if (ExistingAlias != nullptr) { + AddedAliases[Function][Namespace.str()] = ExistingAlias->getName().str(); + return None; + } + + for (const auto &Abbreviation : Abbreviations) { + DeclarationMatcher ConflictMatcher = namedDecl(hasName(Abbreviation)); + const auto HasConflictingChildren = + !match(findAll(ConflictMatcher), *Function, Context).empty(); + const auto HasConflictingAncestors = + !match(functionDecl(hasAncestor(decl(has(ConflictMatcher)))), *Function, + Context) + .empty(); + if (HasConflictingAncestors || HasConflictingChildren) + continue; + + std::string Declaration = + (llvm::Twine("\nnamespace ") + Abbreviation + " = " + Namespace + ";") + .str(); + SourceLocation Loc = + Lexer::getLocForEndOfToken(Function->getBody()->getLocStart(), 0, + SourceMgr, Context.getLangOpts()); + AddedAliases[Function][Namespace.str()] = Abbreviation; + return FixItHint::CreateInsertion(Loc, Declaration); + } + + return None; +} + +std::string NamespaceAliaser::getNamespaceName(ASTContext &Context, + const Stmt &Statement, + StringRef Namespace) const { + const auto *Function = getSurroundingFunction(Context, Statement); + auto FunctionAliases = AddedAliases.find(Function); + if (FunctionAliases != AddedAliases.end()) { + if (FunctionAliases->second.count(Namespace) != 0) { + return FunctionAliases->second.find(Namespace)->getValue(); + } + } + return Namespace.str(); +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/NamespaceAliaser.h b/clang-tidy/utils/NamespaceAliaser.h new file mode 100644 index 000000000..e56d69d3d --- /dev/null +++ b/clang-tidy/utils/NamespaceAliaser.h @@ -0,0 +1,52 @@ +//===---------- NamespaceAliaser.h - clang-tidy ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +// This class creates function-level namespace aliases. +class NamespaceAliaser { +public: + explicit NamespaceAliaser(const SourceManager &SourceMgr); + // Adds a namespace alias for \p Namespace valid near \p + // Statement. Picks the first available name from \p Abbreviations. + // Returns ``llvm::None`` if an alias already exists or there is an error. + llvm::Optional + createAlias(ASTContext &Context, const Stmt &Statement, + llvm::StringRef Namespace, + const std::vector &Abbreviations); + + // Get an alias name for \p Namespace valid at \p Statement. Returns \p + // Namespace if there is no alias. + std::string getNamespaceName(ASTContext &Context, const Stmt &Statement, + llvm::StringRef Namespace) const; + +private: + const SourceManager &SourceMgr; + llvm::DenseMap> + AddedAliases; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_NAMESPACEALIASER_H diff --git a/clang-tidy/utils/OptionsUtils.cpp b/clang-tidy/utils/OptionsUtils.cpp new file mode 100644 index 000000000..0b1d27d50 --- /dev/null +++ b/clang-tidy/utils/OptionsUtils.cpp @@ -0,0 +1,38 @@ +//===--- DanglingHandleCheck.cpp - clang-tidy------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OptionsUtils.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace options { + +static const char StringsDelimiter[] = ";"; + +std::vector parseStringList(StringRef Option) { + SmallVector Names; + Option.split(Names, StringsDelimiter); + std::vector Result; + for (StringRef &Name : Names) { + Name = Name.trim(); + if (!Name.empty()) + Result.push_back(Name); + } + return Result; +} + +std::string serializeStringList(ArrayRef Strings) { + return llvm::join(Strings.begin(), Strings.end(), StringsDelimiter); +} + +} // namespace options +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/OptionsUtils.h b/clang-tidy/utils/OptionsUtils.h new file mode 100644 index 000000000..d822ac98c --- /dev/null +++ b/clang-tidy/utils/OptionsUtils.h @@ -0,0 +1,32 @@ +//===--- DanglingHandleCheck.h - clang-tidy----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OPTIONUTILS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OPTIONUTILS_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace options { + +/// \brief Parse a semicolon separated list of strings. +std::vector parseStringList(StringRef Option); + +/// \brief Serialize a sequence of names that can be parsed by +/// ``parseStringList``. +std::string serializeStringList(ArrayRef Strings); + +} // namespace options +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_OPTIONUTILS_H diff --git a/clang-tidy/utils/TypeTraits.cpp b/clang-tidy/utils/TypeTraits.cpp new file mode 100644 index 000000000..6dd4141ba --- /dev/null +++ b/clang-tidy/utils/TypeTraits.cpp @@ -0,0 +1,149 @@ +//===--- TypeTraits.cpp - clang-tidy---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "TypeTraits.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace type_traits { + +namespace { + +bool classHasTrivialCopyAndDestroy(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + !Record->hasNonTrivialCopyConstructor() && + !Record->hasNonTrivialDestructor(); +} + +bool hasDeletedCopyConstructor(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + if (!Record || !Record->hasDefinition()) + return false; + for (const auto *Constructor : Record->ctors()) { + if (Constructor->isCopyConstructor() && Constructor->isDeleted()) + return true; + } + return false; +} + +} // namespace + +llvm::Optional isExpensiveToCopy(QualType Type, + const ASTContext &Context) { + if (Type->isDependentType() || Type->isIncompleteType()) + return llvm::None; + return !Type.isTriviallyCopyableType(Context) && + !classHasTrivialCopyAndDestroy(Type) && + !hasDeletedCopyConstructor(Type); +} + +bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl, + const ASTContext &Context) { + const auto *ClassDecl = dyn_cast(&RecordDecl); + // Non-C++ records are always trivially constructible. + if (!ClassDecl) + return true; + // A class with a user-provided default constructor is not trivially + // constructible. + if (ClassDecl->hasUserProvidedDefaultConstructor()) + return false; + // A polymorphic class is not trivially constructible + if (ClassDecl->isPolymorphic()) + return false; + // A class is trivially constructible if it has a trivial default constructor. + if (ClassDecl->hasTrivialDefaultConstructor()) + return true; + + // If all its fields are trivially constructible and have no default + // initializers. + for (const FieldDecl *Field : ClassDecl->fields()) { + if (Field->hasInClassInitializer()) + return false; + if (!isTriviallyDefaultConstructible(Field->getType(), Context)) + return false; + } + // If all its direct bases are trivially constructible. + for (const CXXBaseSpecifier &Base : ClassDecl->bases()) { + if (!isTriviallyDefaultConstructible(Base.getType(), Context)) + return false; + if (Base.isVirtual()) + return false; + } + + return true; +} + +// Based on QualType::isTrivial. +bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context) { + if (Type.isNull()) + return false; + + if (Type->isArrayType()) + return isTriviallyDefaultConstructible(Context.getBaseElementType(Type), + Context); + + // Return false for incomplete types after skipping any incomplete array + // types which are expressly allowed by the standard and thus our API. + if (Type->isIncompleteType()) + return false; + + if (Context.getLangOpts().ObjCAutoRefCount) { + switch (Type.getObjCLifetime()) { + case Qualifiers::OCL_ExplicitNone: + return true; + + case Qualifiers::OCL_Strong: + case Qualifiers::OCL_Weak: + case Qualifiers::OCL_Autoreleasing: + return false; + + case Qualifiers::OCL_None: + if (Type->isObjCLifetimeType()) + return false; + break; + } + } + + QualType CanonicalType = Type.getCanonicalType(); + if (CanonicalType->isDependentType()) + return false; + + // As an extension, Clang treats vector types as Scalar types. + if (CanonicalType->isScalarType() || CanonicalType->isVectorType()) + return true; + + if (const auto *RT = CanonicalType->getAs()) { + return recordIsTriviallyDefaultConstructible(*RT->getDecl(), Context); + } + + // No other types can match. + return false; +} + +bool hasNonTrivialMoveConstructor(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + Record->hasNonTrivialMoveConstructor(); +} + +bool hasNonTrivialMoveAssignment(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + Record->hasNonTrivialMoveAssignment(); +} + +} // namespace type_traits +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/TypeTraits.h b/clang-tidy/utils/TypeTraits.h new file mode 100644 index 000000000..ae0b3f0f4 --- /dev/null +++ b/clang-tidy/utils/TypeTraits.h @@ -0,0 +1,43 @@ +//===--- TypeTraits.h - clang-tidy-------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" + +namespace clang { +namespace tidy { +namespace utils { +namespace type_traits { + +/// Returns `true` if `Type` is expensive to copy. +llvm::Optional isExpensiveToCopy(QualType Type, + const ASTContext &Context); + +/// Returns `true` if `Type` is trivially default constructible. +bool isTriviallyDefaultConstructible(QualType Type, const ASTContext &Context); + +/// Returns `true` if `RecordDecl` is trivially default constructible. +bool recordIsTriviallyDefaultConstructible(const RecordDecl &RecordDecl, + const ASTContext &Context); + +/// Returns true if `Type` has a non-trivial move constructor. +bool hasNonTrivialMoveConstructor(QualType Type); + +/// Return true if `Type` has a non-trivial move assignment operator. +bool hasNonTrivialMoveAssignment(QualType Type); + +} // type_traits +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPETRAITS_H diff --git a/clang-tidy/utils/UsingInserter.cpp b/clang-tidy/utils/UsingInserter.cpp new file mode 100644 index 000000000..e7200c99c --- /dev/null +++ b/clang-tidy/utils/UsingInserter.cpp @@ -0,0 +1,89 @@ +//===---------- UsingInserter.cpp - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UsingInserter.h" + +#include "ASTUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tidy { +namespace utils { + +using namespace ast_matchers; + +static StringRef getUnqualifiedName(StringRef QualifiedName) { + size_t LastSeparatorPos = QualifiedName.rfind("::"); + if (LastSeparatorPos == StringRef::npos) + return QualifiedName; + return QualifiedName.drop_front(LastSeparatorPos + 2); +} + +UsingInserter::UsingInserter(const SourceManager &SourceMgr) + : SourceMgr(SourceMgr) {} + +Optional UsingInserter::createUsingDeclaration( + ASTContext &Context, const Stmt &Statement, StringRef QualifiedName) { + StringRef UnqualifiedName = getUnqualifiedName(QualifiedName); + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (!Function) + return None; + + if (AddedUsing.count(std::make_pair(Function, QualifiedName.str())) != 0) + return None; + + SourceLocation InsertLoc = Lexer::getLocForEndOfToken( + Function->getBody()->getLocStart(), 0, SourceMgr, Context.getLangOpts()); + + // Only use using declarations in the main file, not in includes. + if (SourceMgr.getFileID(InsertLoc) != SourceMgr.getMainFileID()) + return None; + + // FIXME: This declaration could be masked. Investigate if + // there is a way to avoid using Sema. + bool AlreadyHasUsingDecl = + !match(stmt(hasAncestor(decl(has(usingDecl(hasAnyUsingShadowDecl( + hasTargetDecl(hasName(QualifiedName.str())))))))), + Statement, Context) + .empty(); + if (AlreadyHasUsingDecl) { + AddedUsing.emplace(NameInFunction(Function, QualifiedName.str())); + return None; + } + // Find conflicting declarations and references. + auto ConflictingDecl = namedDecl(hasName(UnqualifiedName)); + bool HasConflictingDeclaration = + !match(findAll(ConflictingDecl), *Function, Context).empty(); + bool HasConflictingDeclRef = + !match(findAll(declRefExpr(to(ConflictingDecl))), *Function, Context) + .empty(); + if (HasConflictingDeclaration || HasConflictingDeclRef) + return None; + + std::string Declaration = + (llvm::Twine("\nusing ") + QualifiedName + ";").str(); + + AddedUsing.emplace(std::make_pair(Function, QualifiedName.str())); + return FixItHint::CreateInsertion(InsertLoc, Declaration); +} + +StringRef UsingInserter::getShortName(ASTContext &Context, + const Stmt &Statement, + StringRef QualifiedName) { + const FunctionDecl *Function = getSurroundingFunction(Context, Statement); + if (AddedUsing.count(NameInFunction(Function, QualifiedName.str())) != 0) + return getUnqualifiedName(QualifiedName); + return QualifiedName; +} + +} // namespace utils +} // namespace tidy +} // namespace clang diff --git a/clang-tidy/utils/UsingInserter.h b/clang-tidy/utils/UsingInserter.h new file mode 100644 index 000000000..62108e412 --- /dev/null +++ b/clang-tidy/utils/UsingInserter.h @@ -0,0 +1,50 @@ +//===---------- UsingInserter.h - clang-tidy ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H + +#include "clang/AST/Decl.h" +#include "clang/AST/Stmt.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" +#include + +namespace clang { +namespace tidy { +namespace utils { + +// UsingInserter adds using declarations for |QualifiedName| to the surrounding +// function. +// This allows using a shorter name without clobbering other scopes. +class UsingInserter { +public: + UsingInserter(const SourceManager &SourceMgr); + + // Creates a \p using declaration fixit. Returns ``llvm::None`` on error + // or if the using declaration already exists. + llvm::Optional + createUsingDeclaration(ASTContext &Context, const Stmt &Statement, + llvm::StringRef QualifiedName); + + // Returns the unqualified version of the name if there is an + // appropriate using declaration and the qualified name otherwise. + llvm::StringRef getShortName(ASTContext &Context, const Stmt &Statement, + llvm::StringRef QualifiedName); + +private: + typedef std::pair NameInFunction; + const SourceManager &SourceMgr; + std::set AddedUsing; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_USINGINSERTER_H diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 000000000..d6c2456e6 --- /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) + if (SPHINX_FOUND) + include(AddSphinxTarget) + if (${SPHINX_OUTPUT_HTML}) + add_sphinx_target(html clang-tools) + endif() + if (${SPHINX_OUTPUT_MAN}) + add_sphinx_target(man clang-tools) + endif() + endif() +endif() diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 000000000..d674390fb --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,1808 @@ +# Doxyfile 1.7.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = clang-tools-extra + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +# Same directory that Sphinx uses. +OUTPUT_DIRECTORY = ./_build/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../clang-modernize ../clang-apply-replacements ../clang-tidy + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# none of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = clang:: + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +# This directory nicely fits with the way the Sphinx outputs html, so that +# the doxygen documentation will be visible in the doxygen/ path in the web +# output (e.g. github pages). +HTML_OUTPUT = html/doxygen/ + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +# For now, no latex output. +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex/doxygen + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/docs/ModularizeUsage.rst b/docs/ModularizeUsage.rst new file mode 100644 index 000000000..9f0116565 --- /dev/null +++ b/docs/ModularizeUsage.rst @@ -0,0 +1,98 @@ +================ +Modularize Usage +================ + +``modularize [] [|]* +[...]`` + +```` is a place-holder for options +specific to modularize, which are described below in +`Modularize Command Line Options`. + +```` specifies the path of a file name for an +existing module map. The module map must be well-formed in +terms of syntax. Modularize will extract the header file names +from the map. Only normal headers are checked, assuming headers +marked "private", "textual", or "exclude" are not to be checked +as a top-level include, assuming they either are included by +other headers which are checked, or they are not suitable for +modules. + +```` specifies the path of a file name for a +file containing the newline-separated list of headers to check +with respect to each other. Lines beginning with '#' and empty +lines are ignored. Header file names followed by a colon and +other space-separated file names will include those extra files +as dependencies. The file names can be relative or full paths, +but must be on the same line. For example:: + + header1.h + header2.h + header3.h: header1.h header2.h + +Note that unless a ``-prefix (header path)`` option is specified, +non-absolute file paths in the header list file will be relative +to the header list file directory. Use -prefix to specify a different +directory. + +```` is a place-holder for regular Clang +front-end arguments, which must follow the . +Note that by default, modularize assumes .h files +contain C++ source, so if you are using a different language, +you might need to use a ``-x`` option to tell Clang that the +header contains another language, i.e.: ``-x c`` + +Note also that because modularize does not use the clang driver, +you will likely need to pass in additional compiler front-end +arguments to match those passed in by default by the driver. + +Modularize Command Line Options +=============================== + +.. option:: -prefix= + + Prepend the given path to non-absolute file paths in the header list file. + By default, headers are assumed to be relative to the header list file + directory. Use ``-prefix`` to specify a different directory. + +.. option:: -module-map-path= + + Generate a module map and output it to the given file. See the description + in :ref:`module-map-generation`. + +.. option:: -problem-files-list= + + For use only with module map assistant. Input list of files that + have problems with respect to modules. These will still be + included in the generated module map, but will be marked as + "excluded" headers. + +.. option:: -root-module= + + Put modules generated by the -module-map-path option in an enclosing + module with the given name. See the description in :ref:`module-map-generation`. + +.. option:: -block-check-header-list-only + + Limit the #include-inside-extern-or-namespace-block + check to only those headers explicitly listed in the header list. + This is a work-around for avoiding error messages for private includes that + purposefully get included inside blocks. + +.. option:: -no-coverage-check + + Don't do the coverage check for a module map. + +.. option:: -coverage-check-only + + Only do the coverage check for a module map. + +.. option:: -display-file-lists + + Display lists of good files (no compile errors), problem files, + and a combined list with problem files preceded by a '#'. + This can be used to quickly determine which files have problems. + The latter combined list might be useful in starting to modularize + a set of headers. You can start with a full list of headers, + use -display-file-lists option, and then use the combined list as + your intermediate list, uncommenting-out headers as you fix them. diff --git a/docs/README.txt b/docs/README.txt new file mode 100644 index 000000000..4b6077758 --- /dev/null +++ b/docs/README.txt @@ -0,0 +1,11 @@ +------------------------------------------------------------- +Documentation for the tools of clang-tools-extra repo project +------------------------------------------------------------- + +Sphinx and doxygen documentation is generated by executing make. + +Sphinx html files can be generated separately using make html. + +Doxygen html files can also be generated using make doxygen. + +The generated documentation will be placed in _build/html. diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst new file mode 100644 index 000000000..becdaae56 --- /dev/null +++ b/docs/ReleaseNotes.rst @@ -0,0 +1,180 @@ +===================================== +Extra Clang Tools 4.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 4.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 4.0.0? +====================================== + +Some of the major new features and improvements to Extra Clang Tools are listed +here. + +Improvements to clang-tidy +-------------------------- + +- New `cppcoreguidelines-slicing + `_ check + + Flags slicing of member variables or vtable. + +- New `cppcoreguidelines-special-member-functions + `_ check + + Flags classes where some, but not all, special member functions are user-defined. + +- The UseCERTSemantics option for the `misc-move-constructor-init + `_ check + has been removed as it duplicated the `modernize-pass-by-value + `_ check. + +- New `misc-move-forwarding-reference + `_ check + + Warns when ``std::move`` is applied to a forwarding reference instead of + ``std::forward``. + +- `misc-pointer-and-integral-operation` check was removed. + +- New `misc-string-compare + `_ check + + Warns about using ``compare`` to test for string equality or inequality. + +- New `misc-use-after-move + `_ check + + Warns if an object is used after it has been moved, without an intervening + reinitialization. + +- New `cppcoreguidelines-no-malloc + `_ check + warns if C-style memory management is used and suggests the use of RAII. + +- `modernize-make-unique + `_ + and `modernize-make-shared + `_ + now handle calls to the smart pointer's ``reset()`` method. + +- The `modernize-pass-by-value + `_ check + now has a ValuesOnly option to only warn about parameters that are passed by + value but not moved. + +- The `modernize-use-auto + `_ check + now warns about variable declarations that are initialized with a cast, or by + calling a templated function that behaves as a cast. + +- The modernize-use-default check has been renamed to `modernize-use-equals-default + `_. + +- New `modernize-use-default-member-init + `_ check + + Converts a default constructor's member initializers into default member initializers. + Removes member initializers that are the same as a default member initializer. + +- New `modernize-use-equals-delete + `_ check + + Adds ``= delete`` to unimplemented private special member functions. + +- New `modernize-use-transparent-functors + `_ check + + Replaces uses of non-transparent functors with transparent ones where applicable. + +- New `mpi-buffer-deref + `_ check + + Flags buffers which are insufficiently dereferenced when passed to an MPI function call. + +- New `mpi-type-mismatch + `_ check + + Flags MPI function calls with a buffer type and MPI data type mismatch. + +- New `performance-inefficient-string-concatenation + `_ check + + Warns about the performance overhead arising from concatenating strings using + the ``operator+``, instead of ``operator+=``. + +- New `performance-type-promotion-in-math-fn + `_ check + + Replaces uses of C-style standard math functions with double parameters and float + arguments with an equivalent function that takes a float parameter. + +- `readability-container-size-empty + `_ check + supports arbitrary containers with with suitable ``empty()`` and ``size()`` + methods. + +- New `readability-misplaced-array-index + `_ check + + Warns when there is array index before the [] instead of inside it. + +- New `readability-non-const-parameter + `_ check + + Flags function parameters of a pointer type that could be changed to point to + a constant type instead. + +- New `readability-redundant-declaration + `_ check + + Finds redundant variable and function declarations. + +- New `readability-redundant-function-ptr-dereference + `_ check + + Finds redundant function pointer dereferences. + +- New `readability-redundant-member-init + `_ check + + Flags member initializations that are unnecessary because the same default + constructor would be called if they were not present. + +- The `readability-redundant-string-cstr + `_ check + now warns about redundant calls to data() too. + +- The `google-explicit-constructor + `_ check + now warns about conversion operators not marked explicit. + +Fixed bugs: + +- `modernize-make-unique + `_ + and `modernize-make-shared + `_ + Calling ``make_{unique|shared}`` from within a member function of a type + with a private or protected constructor would be ill-formed. + +Improvements to include-fixer +----------------------------- + +- Emacs integration was added. 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..bffedbff7 --- /dev/null +++ b/docs/clang-rename.rst @@ -0,0 +1,165 @@ +============ +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 + -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/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/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-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-dcl59-cpp.rst b/docs/clang-tidy/checks/cert-dcl59-cpp.rst new file mode 100644 index 000000000..9528c0477 --- /dev/null +++ b/docs/clang-tidy/checks/cert-dcl59-cpp.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-dcl59-cpp +.. meta:: + :http-equiv=refresh: 5;URL=google-build-namespaces.html + +cert-dcl59-cpp +============== + +The cert-dcl59-cpp check is an alias, please see +`google-build-namespaces `_ for more information. diff --git a/docs/clang-tidy/checks/cert-env33-c.rst b/docs/clang-tidy/checks/cert-env33-c.rst new file mode 100644 index 000000000..c5321b07f --- /dev/null +++ b/docs/clang-tidy/checks/cert-env33-c.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - cert-env33-c + +cert-env33-c +============ + +This check flags calls to ``system()``, ``popen()``, and ``_popen()``, which +execute a command processor. It does not flag calls to ``system()`` with a null +pointer argument, as such a call checks for the presence of a command processor +but does not actually attempt to execute a command. + +This check corresponds to the CERT C Coding Standard rule +`ENV33-C. Do not call system() +`_. diff --git a/docs/clang-tidy/checks/cert-err09-cpp.rst b/docs/clang-tidy/checks/cert-err09-cpp.rst new file mode 100644 index 000000000..1494bcc07 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err09-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err09-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-throw-by-value-catch-by-reference.html + +cert-err09-cpp +============== + +The cert-err09-cpp check is an alias, please see +`misc-throw-by-value-catch-by-reference `_ +for more information. diff --git a/docs/clang-tidy/checks/cert-err34-c.rst b/docs/clang-tidy/checks/cert-err34-c.rst new file mode 100644 index 000000000..362aef209 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err34-c.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - cert-err34-c + +cert-err34-c +============ + +This check flags calls to string-to-number conversion functions that do not +verify the validity of the conversion, such as ``atoi()`` or ``scanf()``. It +does not flag calls to ``strtol()``, or other, related conversion functions that +do perform better error checking. + +.. code-block:: c + + #include + + void func(const char *buff) { + int si; + + if (buff) { + si = atoi(buff); /* 'atoi' used to convert a string to an integer, but function will + not report conversion errors; consider using 'strtol' instead. */ + } else { + /* Handle error */ + } + } + +This check corresponds to the CERT C Coding Standard rule +`ERR34-C. Detect errors when converting a string to a number +`_. diff --git a/docs/clang-tidy/checks/cert-err52-cpp.rst b/docs/clang-tidy/checks/cert-err52-cpp.rst new file mode 100644 index 000000000..a29dc7bcd --- /dev/null +++ b/docs/clang-tidy/checks/cert-err52-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err52-cpp + +cert-err52-cpp +============== + +This check flags all call expressions involving ``setjmp()`` and ``longjmp()``. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR52-CPP. Do not use setjmp() or longjmp() +`_. diff --git a/docs/clang-tidy/checks/cert-err58-cpp.rst b/docs/clang-tidy/checks/cert-err58-cpp.rst new file mode 100644 index 000000000..6a7f615db --- /dev/null +++ b/docs/clang-tidy/checks/cert-err58-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-err58-cpp + +cert-err58-cpp +============== + +This check flags all ``static`` or ``thread_local`` variable declarations where +the initializer for the object may throw an exception. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR58-CPP. Handle all exceptions thrown before main() begins executing +`_. diff --git a/docs/clang-tidy/checks/cert-err60-cpp.rst b/docs/clang-tidy/checks/cert-err60-cpp.rst new file mode 100644 index 000000000..9fcb840fc --- /dev/null +++ b/docs/clang-tidy/checks/cert-err60-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-err60-cpp + +cert-err60-cpp +============== + +This check flags all throw expressions where the exception object is not nothrow +copy constructible. + +This check corresponds to the CERT C++ Coding Standard rule +`ERR60-CPP. Exception objects must be nothrow copy constructible +`_. diff --git a/docs/clang-tidy/checks/cert-err61-cpp.rst b/docs/clang-tidy/checks/cert-err61-cpp.rst new file mode 100644 index 000000000..f0cd0fee8 --- /dev/null +++ b/docs/clang-tidy/checks/cert-err61-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-err61-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-throw-by-value-catch-by-reference.html + +cert-err61-cpp +============== + +The cert-err61-cpp check is an alias, please see +`misc-throw-by-value-catch-by-reference `_ +for more information. diff --git a/docs/clang-tidy/checks/cert-fio38-c.rst b/docs/clang-tidy/checks/cert-fio38-c.rst new file mode 100644 index 000000000..5ce37f442 --- /dev/null +++ b/docs/clang-tidy/checks/cert-fio38-c.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-fio38-c +.. meta:: + :http-equiv=refresh: 5;URL=misc-non-copyable-objects.html + +cert-fio38-c +============ + +The cert-fio38-c check is an alias, please see +`misc-non-copyable-objects `_ for more +information. diff --git a/docs/clang-tidy/checks/cert-flp30-c.rst b/docs/clang-tidy/checks/cert-flp30-c.rst new file mode 100644 index 000000000..c37b63980 --- /dev/null +++ b/docs/clang-tidy/checks/cert-flp30-c.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-flp30-c + +cert-flp30-c +============ + +This check flags ``for`` loops where the induction expression has a +floating-point type. + +This check corresponds to the CERT C Coding Standard rule +`FLP30-C. Do not use floating-point variables as loop counters +`_. diff --git a/docs/clang-tidy/checks/cert-msc30-c.rst b/docs/clang-tidy/checks/cert-msc30-c.rst new file mode 100644 index 000000000..afd9b1ad5 --- /dev/null +++ b/docs/clang-tidy/checks/cert-msc30-c.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - cert-msc30-c +.. meta:: + :http-equiv=refresh: 5;URL=cert-msc50-cpp.html + +cert-msc30-c +============ + +The cert-msc30-c check is an alias, please see +`cert-msc50-cpp `_ for more information. diff --git a/docs/clang-tidy/checks/cert-msc50-cpp.rst b/docs/clang-tidy/checks/cert-msc50-cpp.rst new file mode 100644 index 000000000..debf01cf2 --- /dev/null +++ b/docs/clang-tidy/checks/cert-msc50-cpp.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - cert-msc50-cpp + +cert-msc50-cpp +============== + +Pseudorandom number generators use mathematical algorithms to produce a sequence +of numbers with good statistical properties, but the numbers produced are not +genuinely random. The ``std::rand()`` function takes a seed (number), runs a +mathematical operation on it and returns the result. By manipulating the seed +the result can be predictable. This check warns for the usage of +``std::rand()``. diff --git a/docs/clang-tidy/checks/cert-oop11-cpp.rst b/docs/clang-tidy/checks/cert-oop11-cpp.rst new file mode 100644 index 000000000..c824528c5 --- /dev/null +++ b/docs/clang-tidy/checks/cert-oop11-cpp.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - cert-oop11-cpp +.. meta:: + :http-equiv=refresh: 5;URL=misc-move-constructor-init.html + +cert-oop11-cpp +============== + +The cert-oop11-cpp check is an alias, please see +`misc-move-constructor-init `_ for more +information. diff --git a/docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst b/docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst new file mode 100644 index 000000000..490785846 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - cppcoreguidelines-interfaces-global-init + +cppcoreguidelines-interfaces-global-init +======================================== + +This check flags initializers of globals that access extern objects, +and therefore can lead to order-of-initialization problems. + +This rule is part of the "Interfaces" profile of the C++ Core Guidelines, see +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Ri-global-init + +Note that currently this does not flag calls to non-constexpr functions, and +therefore globals could still be accessed from functions themselves. + diff --git a/docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst b/docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst new file mode 100644 index 000000000..7618505fa --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-no-malloc.rst @@ -0,0 +1,27 @@ +.. 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. +See `C++ Core Guidelines +`. + +There is no attempt made to provide fixit 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)); + 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..1d29af51a --- /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 fixit 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..bcc012a82 --- /dev/null +++ b/docs/clang-tidy/checks/cppcoreguidelines-special-member-functions.rst @@ -0,0 +1,21 @@ +.. 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. diff --git a/docs/clang-tidy/checks/google-build-explicit-make-pair.rst b/docs/clang-tidy/checks/google-build-explicit-make-pair.rst new file mode 100644 index 000000000..e3e9eeb0e --- /dev/null +++ b/docs/clang-tidy/checks/google-build-explicit-make-pair.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-build-explicit-make-pair + +google-build-explicit-make-pair +=============================== + +Check that ``make_pair``'s template arguments are deduced. + +G++ 4.6 in C++11 mode fails badly if ``make_pair``'s template arguments are +specified explicitly, and such use isn't intended in any case. + +Corresponding cpplint.py check name: `build/explicit_make_pair`. diff --git a/docs/clang-tidy/checks/google-build-namespaces.rst b/docs/clang-tidy/checks/google-build-namespaces.rst new file mode 100644 index 000000000..69d124de6 --- /dev/null +++ b/docs/clang-tidy/checks/google-build-namespaces.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - google-build-namespaces + +google-build-namespaces +======================= + +`cert-dcl59-cpp` redirects here as an alias for this check. + +Finds anonymous namespaces in headers. + +https://google.github.io/styleguide/cppguide.html#Namespaces + +Corresponding cpplint.py check name: `build/namespaces`. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). diff --git a/docs/clang-tidy/checks/google-build-using-namespace.rst b/docs/clang-tidy/checks/google-build-using-namespace.rst new file mode 100644 index 000000000..f01bfedad --- /dev/null +++ b/docs/clang-tidy/checks/google-build-using-namespace.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - google-build-using-namespace + +google-build-using-namespace +============================ + +Finds ``using namespace`` directives. + +The check implements the following rule of the +`Google C++ Style Guide `_: + + You may not use a using-directive to make all names from a namespace + available. + + .. code-block:: c++ + + // Forbidden -- This pollutes the namespace. + using namespace foo; + +Corresponding cpplint.py check name: `build/namespaces`. diff --git a/docs/clang-tidy/checks/google-default-arguments.rst b/docs/clang-tidy/checks/google-default-arguments.rst new file mode 100644 index 000000000..c02099c6e --- /dev/null +++ b/docs/clang-tidy/checks/google-default-arguments.rst @@ -0,0 +1,8 @@ +.. title:: clang-tidy - google-default-arguments + +google-default-arguments +======================== + +Checks that default arguments are not given for virtual methods. + +See https://google.github.io/styleguide/cppguide.html#Default_Arguments diff --git a/docs/clang-tidy/checks/google-explicit-constructor.rst b/docs/clang-tidy/checks/google-explicit-constructor.rst new file mode 100644 index 000000000..acafc1acf --- /dev/null +++ b/docs/clang-tidy/checks/google-explicit-constructor.rst @@ -0,0 +1,56 @@ +.. title:: clang-tidy - google-explicit-constructor + +google-explicit-constructor +=========================== + + +Checks that constructors callable with a single argument and conversion +operators are marked explicit to avoid the risk of unintentional implicit +conversions. + +Consider this example: + +.. code-block:: c++ + + struct S { + int x; + operator bool() const { return true; } + }; + + bool f() { + S a{1}; + S b{2}; + return a == b; + } + +The function will return ``true``, since the objects are implicitly converted to +``bool`` before comparison, which is unlikely to be the intent. + +The check will suggest inserting ``explicit`` before the constructor or +conversion operator declaration. However, copy and move constructors should not +be explicit, as well as constructors taking a single ``initializer_list`` +argument. + +This code: + +.. code-block:: c++ + + struct S { + S(int a); + explicit S(const S&); + operator bool() const; + ... + +will become + +.. code-block:: c++ + + struct S { + explicit S(int a); + S(const S&); + explicit operator bool() const; + ... + + + +See https://google.github.io/styleguide/cppguide.html#Explicit_Constructors diff --git a/docs/clang-tidy/checks/google-global-names-in-headers.rst b/docs/clang-tidy/checks/google-global-names-in-headers.rst new file mode 100644 index 000000000..88ba90668 --- /dev/null +++ b/docs/clang-tidy/checks/google-global-names-in-headers.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - google-global-names-in-headers + +google-global-names-in-headers +============================== + +Flag global namespace pollution in header files. Right now it only triggers on +``using`` declarations and directives. + +The relevant style guide section is +https://google.github.io/styleguide/cppguide.html#Namespaces. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not contain "." prefix). Default is "h". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). diff --git a/docs/clang-tidy/checks/google-readability-braces-around-statements.rst b/docs/clang-tidy/checks/google-readability-braces-around-statements.rst new file mode 100644 index 000000000..e5c8eb64c --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-braces-around-statements.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-readability-braces-around-statements +.. meta:: + :http-equiv=refresh: 5;URL=readability-braces-around-statements.html + +google-readability-braces-around-statements +=========================================== + +The google-readability-braces-around-statements check is an alias, please see +`readability-braces-around-statements `_ +for more information. diff --git a/docs/clang-tidy/checks/google-readability-casting.rst b/docs/clang-tidy/checks/google-readability-casting.rst new file mode 100644 index 000000000..4c9d1bc4f --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-casting.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - google-readability-casting + +google-readability-casting +========================== + +Finds usages of C-style casts. + +https://google.github.io/styleguide/cppguide.html#Casting + +Corresponding cpplint.py check name: `readability/casting`. + +This check is similar to `-Wold-style-cast`, but it suggests automated fixes +in some cases. The reported locations should not be different from the +ones generated by `-Wold-style-cast`. diff --git a/docs/clang-tidy/checks/google-readability-function-size.rst b/docs/clang-tidy/checks/google-readability-function-size.rst new file mode 100644 index 000000000..b4546284c --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-function-size.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-readability-function-size +.. meta:: + :http-equiv=refresh: 5;URL=readability-function-size.html + +google-readability-function-size +================================ + +The google-readability-function-size check is an alias, please see +`readability-function-size `_ for more +information. diff --git a/docs/clang-tidy/checks/google-readability-namespace-comments.rst b/docs/clang-tidy/checks/google-readability-namespace-comments.rst new file mode 100644 index 000000000..258a30563 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-namespace-comments.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - google-readability-namespace-comments +.. meta:: + :http-equiv=refresh: 5;URL=llvm-namespace-comment.html + +google-readability-namespace-comments +===================================== + +The google-readability-namespace-comments check is an alias, please see +`llvm-namespace-comment `_ for more information. diff --git a/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst new file mode 100644 index 000000000..e77fd64a3 --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-redundant-smartptr-get.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-readability-redundant-smartptr-get +.. meta:: + :http-equiv=refresh: 5;URL=readability-redundant-smartptr-get.html + +google-readability-redundant-smartptr-get +========================================= + +The google-readability-redundant-smartptr-get check is an alias, please see +`readability-redundant-smartptr-get `_ +for more information. diff --git a/docs/clang-tidy/checks/google-readability-todo.rst b/docs/clang-tidy/checks/google-readability-todo.rst new file mode 100644 index 000000000..159d2b4ad --- /dev/null +++ b/docs/clang-tidy/checks/google-readability-todo.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - google-readability-todo + +google-readability-todo +======================= + +Finds TODO comments without a username or bug number. + +The relevant style guide section is +https://google.github.io/styleguide/cppguide.html#TODO_Comments. + +Corresponding cpplint.py check: `readability/todo` diff --git a/docs/clang-tidy/checks/google-runtime-int.rst b/docs/clang-tidy/checks/google-runtime-int.rst new file mode 100644 index 000000000..5e878a4a3 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-int.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - google-runtime-int + +google-runtime-int +================== + +Finds uses of ``short``, ``long`` and ``long long`` and suggest replacing them +with ``u?intXX(_t)?``. + +The corresponding style guide rule: +https://google.github.io/styleguide/cppguide.html#Integer_Types. + +Correspondig cpplint.py check: `runtime/int`. + +Options +------- + +.. option:: UnsignedTypePrefix + + A string specifying the unsigned type prefix. Default is `uint`. + +.. option:: SignedTypePrefix + + A string specifying the signed type prefix. Default is `int`. + +.. option:: TypeSuffix + + A string specifying the type suffix. Default is an empty string. diff --git a/docs/clang-tidy/checks/google-runtime-member-string-references.rst b/docs/clang-tidy/checks/google-runtime-member-string-references.rst new file mode 100644 index 000000000..bc4f0ee87 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-member-string-references.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - google-runtime-member-string-references + +google-runtime-member-string-references +======================================= + +Finds members of type ``const string&``. + +const string reference members are generally considered unsafe as they can be +created from a temporary quite easily. + +.. code-block:: c++ + + struct S { + S(const string &Str) : Str(Str) {} + const string &Str; + }; + S instance("string"); + +In the constructor call a string temporary is created from ``const char *`` and +destroyed immediately after the call. This leaves around a dangling reference. + +This check emit warnings for both ``std::string`` and ``::string`` const +reference members. + +Corresponding cpplint.py check name: `runtime/member_string_reference`. diff --git a/docs/clang-tidy/checks/google-runtime-memset.rst b/docs/clang-tidy/checks/google-runtime-memset.rst new file mode 100644 index 000000000..3832b6e44 --- /dev/null +++ b/docs/clang-tidy/checks/google-runtime-memset.rst @@ -0,0 +1,10 @@ +.. title:: clang-tidy - google-runtime-memset + +google-runtime-memset +===================== + +Finds calls to ``memset`` with a literal zero in the length argument. + +This is most likely unintended and the length and value arguments are swapped. + +Corresponding cpplint.py check name: `runtime/memset`. 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/list.rst b/docs/clang-tidy/checks/list.rst new file mode 100644 index 000000000..6cdd79997 --- /dev/null +++ b/docs/clang-tidy/checks/list.rst @@ -0,0 +1,154 @@ +.. title:: clang-tidy - Clang-Tidy Checks + +Clang-Tidy Checks +================= + +.. toctree:: + boost-use-to-string + cert-dcl03-c (redirects to misc-static-assert) + cert-dcl50-cpp + cert-dcl54-cpp (redirects to misc-new-delete-overloads) + cert-dcl59-cpp (redirects to google-build-namespaces) + cert-env33-c + cert-err09-cpp (redirects to misc-throw-by-value-catch-by-reference) + cert-err34-c + cert-err52-cpp + cert-err58-cpp + cert-err60-cpp + cert-err61-cpp (redirects to misc-throw-by-value-catch-by-reference) + cert-fio38-c (redirects to misc-non-copyable-objects) + cert-flp30-c + cert-msc30-c (redirects to cert-msc50-cpp) + cert-msc50-cpp + cert-oop11-cpp (redirects to misc-move-constructor-init) + cppcoreguidelines-interfaces-global-init + cppcoreguidelines-no-malloc + cppcoreguidelines-pro-bounds-array-to-pointer-decay + cppcoreguidelines-pro-bounds-constant-array-index + cppcoreguidelines-pro-bounds-pointer-arithmetic + cppcoreguidelines-pro-type-const-cast + cppcoreguidelines-pro-type-cstyle-cast + cppcoreguidelines-pro-type-member-init + cppcoreguidelines-pro-type-reinterpret-cast + cppcoreguidelines-pro-type-static-cast-downcast + cppcoreguidelines-pro-type-union-access + cppcoreguidelines-pro-type-vararg + cppcoreguidelines-slicing + cppcoreguidelines-special-member-functions + google-build-explicit-make-pair + google-build-namespaces + google-build-using-namespace + google-default-arguments + google-explicit-constructor + google-global-names-in-headers + google-readability-braces-around-statements (redirects to readability-braces-around-statements) + google-readability-casting + google-readability-function-size (redirects to readability-function-size) + google-readability-namespace-comments (redirects to llvm-namespace-comment) + google-readability-redundant-smartptr-get (redirects to readability-redundant-smartptr-get) + google-readability-todo + google-runtime-int + google-runtime-member-string-references + google-runtime-memset + google-runtime-operator + google-runtime-references + llvm-header-guard + llvm-include-order + llvm-namespace-comment + llvm-twine-local + misc-argument-comment + misc-assert-side-effect + misc-bool-pointer-implicit-conversion + misc-dangling-handle + misc-definitions-in-headers + misc-fold-init-type + misc-forward-declaration-namespace + misc-inaccurate-erase + misc-incorrect-roundings + misc-inefficient-algorithm + misc-macro-parentheses + misc-macro-repeated-side-effects + misc-misplaced-const + misc-misplaced-widening-cast + misc-move-const-arg + misc-move-constructor-init + misc-move-forwarding-reference + misc-multiple-statement-macro + misc-new-delete-overloads + misc-noexcept-move-constructor + misc-non-copyable-objects + misc-redundant-expression + misc-sizeof-container + misc-sizeof-expression + misc-static-assert + misc-string-compare + misc-string-constructor + misc-string-integer-assignment + misc-string-literal-with-embedded-nul + misc-suspicious-enum-usage + misc-suspicious-missing-comma + misc-suspicious-semicolon + misc-suspicious-string-compare + misc-swapped-arguments + misc-throw-by-value-catch-by-reference + misc-unconventional-assign-operator + misc-undelegated-constructor + misc-uniqueptr-reset-release + misc-unused-alias-decls + misc-unused-parameters + misc-unused-raii + misc-unused-using-decls + misc-use-after-move + misc-virtual-near-miss + modernize-avoid-bind + modernize-deprecated-headers + modernize-loop-convert + modernize-make-shared + modernize-make-unique + modernize-pass-by-value + modernize-raw-string-literal + modernize-redundant-void-arg + modernize-replace-auto-ptr + modernize-shrink-to-fit + 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-nullptr + modernize-use-override + modernize-use-transparent-functors + modernize-use-using + mpi-buffer-deref + mpi-type-mismatch + performance-faster-string-find + performance-for-range-copy + performance-implicit-cast-in-loop + performance-inefficient-string-concatenation + performance-type-promotion-in-math-fn + performance-unnecessary-copy-initialization + performance-unnecessary-value-param + readability-avoid-const-params-in-decls + readability-braces-around-statements + readability-container-size-empty + readability-delete-null-pointer + readability-deleted-default + readability-else-after-return + readability-function-size + readability-identifier-naming + readability-implicit-bool-cast + readability-inconsistent-declaration-parameter-name + readability-misplaced-array-index + readability-named-parameter + readability-non-const-parameter + readability-redundant-control-flow + readability-redundant-declaration + readability-redundant-function-ptr-dereference + readability-redundant-member-init + readability-redundant-smartptr-get + readability-redundant-string-cstr + readability-redundant-string-init + readability-simplify-boolean-expr + readability-static-definition-in-anonymous-namespace + readability-uniqueptr-delete-release diff --git a/docs/clang-tidy/checks/llvm-header-guard.rst b/docs/clang-tidy/checks/llvm-header-guard.rst new file mode 100644 index 000000000..58233ecac --- /dev/null +++ b/docs/clang-tidy/checks/llvm-header-guard.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - llvm-header-guard + +llvm-header-guard +================= + +Finds and fixes header guards that do not adhere to LLVM style. + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). diff --git a/docs/clang-tidy/checks/llvm-include-order.rst b/docs/clang-tidy/checks/llvm-include-order.rst new file mode 100644 index 000000000..dba98376c --- /dev/null +++ b/docs/clang-tidy/checks/llvm-include-order.rst @@ -0,0 +1,9 @@ +.. title:: clang-tidy - llvm-include-order + +llvm-include-order +================== + + +Checks the correct order of ``#includes``. + +See http://llvm.org/docs/CodingStandards.html#include-style diff --git a/docs/clang-tidy/checks/llvm-namespace-comment.rst b/docs/clang-tidy/checks/llvm-namespace-comment.rst new file mode 100644 index 000000000..881b202f9 --- /dev/null +++ b/docs/clang-tidy/checks/llvm-namespace-comment.rst @@ -0,0 +1,28 @@ +.. 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 + +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..5ce0302b5 --- /dev/null +++ b/docs/clang-tidy/checks/llvm-twine-local.rst @@ -0,0 +1,8 @@ +.. 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. diff --git a/docs/clang-tidy/checks/misc-argument-comment.rst b/docs/clang-tidy/checks/misc-argument-comment.rst new file mode 100644 index 000000000..edc4f3083 --- /dev/null +++ b/docs/clang-tidy/checks/misc-argument-comment.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - misc-argument-comment + +misc-argument-comment +===================== + +Checks that argument comments match parameter names. + +The check understands argument comments in the form ``/*parameter_name=*/`` +that are placed right before the argument. + +.. code-block:: c++ + + void f(bool foo); + + ... + + f(/*bar=*/true); + // warning: argument name 'bar' in comment does not match parameter name 'foo' + +The check tries to detect typos and suggest automated fixes for them. + +Options +------- + +.. option:: StrictMode + + When non-zero, the check will ignore leading and trailing underscores and + case when comparing parameter names. diff --git a/docs/clang-tidy/checks/misc-assert-side-effect.rst b/docs/clang-tidy/checks/misc-assert-side-effect.rst new file mode 100644 index 000000000..3f8b46967 --- /dev/null +++ b/docs/clang-tidy/checks/misc-assert-side-effect.rst @@ -0,0 +1,23 @@ +.. title:: clang-tidy - misc-assert-side-effect + +misc-assert-side-effect +======================= + +Finds ``assert()`` with side effect. + +The condition of ``assert()`` is evaluated only in debug builds so a +condition with side effect can cause different behavior in debug / release +builds. + +Options +------- + +.. option:: AssertMacros + + A comma-separated list of the names of assert macros to be checked. + +.. option:: CheckFunctionCalls + + Whether to treat non-const member and non-member functions as they produce + side effects. Disabled by default because it can increase the number of false + positive warnings. diff --git a/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst b/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst new file mode 100644 index 000000000..db9a2f4a6 --- /dev/null +++ b/docs/clang-tidy/checks/misc-bool-pointer-implicit-conversion.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-bool-pointer-implicit-conversion + +misc-bool-pointer-implicit-conversion +===================================== + +Checks for conditions based on implicit conversion from a ``bool`` pointer to +``bool``. + +Example: + +.. code-block:: c++ + + bool *p; + if (p) { + // Never used in a pointer-specific way. + } diff --git a/docs/clang-tidy/checks/misc-dangling-handle.rst b/docs/clang-tidy/checks/misc-dangling-handle.rst new file mode 100644 index 000000000..03d03ed99 --- /dev/null +++ b/docs/clang-tidy/checks/misc-dangling-handle.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - misc-dangling-handle + +misc-dangling-handle +==================== + +Detect dangling references in value handles like +``std::experimental::string_view``. +These dangling references can be a result of constructing handles from temporary +values, where the temporary is destroyed soon after the handle is created. + +Examples: + +.. code-block:: c++ + + string_view View = string(); // View will dangle. + string A; + View = A + "A"; // still dangle. + + vector V; + V.push_back(string()); // V[0] is dangling. + V.resize(3, string()); // V[1] and V[2] will also dangle. + + string_view f() { + // All these return values will dangle. + return string(); + string S; + return S; + char Array[10]{}; + return Array; + } + +Options +------- + +.. option:: HandleClasses + + A semicolon-separated list of class names that should be treated as handles. + By default only ``std::experimental::basic_string_view`` is considered. diff --git a/docs/clang-tidy/checks/misc-definitions-in-headers.rst b/docs/clang-tidy/checks/misc-definitions-in-headers.rst new file mode 100644 index 000000000..32e7b5e31 --- /dev/null +++ b/docs/clang-tidy/checks/misc-definitions-in-headers.rst @@ -0,0 +1,91 @@ +.. title:: clang-tidy - misc-definitions-in-headers + +misc-definitions-in-headers +=========================== + +Finds non-extern non-inline function and variable definitions in header files, +which can lead to potential ODR violations in case these headers are included +from multiple translation units. + +.. code-block:: c++ + + // Foo.h + int a = 1; // Warning: variable definition. + extern int d; // OK: extern variable. + + namespace N { + int e = 2; // Warning: variable definition. + } + + // Warning: variable definition. + const char* str = "foo"; + + // OK: internal linkage variable definitions are ignored for now. + // Although these might also cause ODR violations, we can be less certain and + // should try to keep the false-positive rate down. + static int b = 1; + const int c = 1; + const char* const str2 = "foo"; + + // Warning: function definition. + int g() { + return 1; + } + + // OK: inline function definition is allowed to be defined multiple times. + inline int e() { + return 1; + } + + class A { + public: + int f1() { return 1; } // OK: implicitly inline member function definition is allowed. + int f2(); + + static int d; + }; + + // Warning: not an inline member function definition. + int A::f2() { return 1; } + + // OK: class static data member declaration is allowed. + int A::d = 1; + + // OK: function template is allowed. + template + T f3() { + T a = 1; + return a; + } + + // Warning: full specialization of a function template is not allowed. + template <> + int f3() { + int a = 1; + return a; + } + + template + struct B { + void f1(); + }; + + // OK: member function definition of a class template is allowed. + template + void B::f1() {} + +Options +------- + +.. option:: HeaderFileExtensions + + A comma-separated list of filename extensions of header files (the filename + extensions should not include "." prefix). Default is "h,hh,hpp,hxx". + For header files without an extension, use an empty string (if there are no + other desired extensions) or leave an empty element in the list. e.g., + "h,hh,hpp,hxx," (note the trailing comma). + +.. option:: UseHeaderFileExtension + + When non-zero, the check will use the file extension to distinguish header + files. Default is `1`. diff --git a/docs/clang-tidy/checks/misc-fold-init-type.rst b/docs/clang-tidy/checks/misc-fold-init-type.rst new file mode 100644 index 000000000..ff1c6f0fd --- /dev/null +++ b/docs/clang-tidy/checks/misc-fold-init-type.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - misc-fold-init-type + +misc-fold-init-type +=================== + +The check flags type mismatches in +`folds `_ +like ``std::accumulate`` that might result in loss of precision. +``std::accumulate`` folds an input range into an initial value using the type of +the latter, with ``operator+`` by default. This can cause loss of precision +through: + +- Truncation: The following code uses a floating point range and an int + initial value, so trucation wil happen at every application of ``operator+`` + and the result will be `0`, which might not be what the user expected. + +.. code-block:: c++ + + auto a = {0.5f, 0.5f, 0.5f, 0.5f}; + return std::accumulate(std::begin(a), std::end(a), 0); + +- Overflow: The following code also returns `0`. + +.. code-block:: c++ + + auto a = {65536LL * 65536 * 65536}; + return std::accumulate(std::begin(a), std::end(a), 0); diff --git a/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst b/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst new file mode 100644 index 000000000..2dd1c463b --- /dev/null +++ b/docs/clang-tidy/checks/misc-forward-declaration-namespace.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - misc-forward-declaration-namespace + +misc-forward-declaration-namespace +================================== + +Checks if an unused forward declaration is in a wrong namespace. + +The check inspects all unused forward declarations and checks if there is any +declaration/definition with the same name existing, which could indicate that +the forward declaration is in a potentially wrong namespace. + +.. code-block:: c++ + + namespace na { struct A; } + namespace nb { struct A {}; } + nb::A a; + // warning : no definition found for 'A', but a definition with the same name + // 'A' found in another namespace 'nb::' + +This check can only generate warnings, but it can't suggest a fix at this point. diff --git a/docs/clang-tidy/checks/misc-inaccurate-erase.rst b/docs/clang-tidy/checks/misc-inaccurate-erase.rst new file mode 100644 index 000000000..f55bfa73b --- /dev/null +++ b/docs/clang-tidy/checks/misc-inaccurate-erase.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-inaccurate-erase + +misc-inaccurate-erase +===================== + + +Checks for inaccurate use of the ``erase()`` method. + +Algorithms like ``remove()`` do not actually remove any element from the +container but return an iterator to the first redundant element at the end +of the container. These redundant elements must be removed using the +``erase()`` method. This check warns when not all of the elements will be +removed due to using an inappropriate overload. diff --git a/docs/clang-tidy/checks/misc-incorrect-roundings.rst b/docs/clang-tidy/checks/misc-incorrect-roundings.rst new file mode 100644 index 000000000..be04f6cf1 --- /dev/null +++ b/docs/clang-tidy/checks/misc-incorrect-roundings.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-incorrect-roundings + +misc-incorrect-roundings +======================== + +Checks the usage of patterns known to produce incorrect rounding. +Programmers often use:: + + (int)(double_expression + 0.5) + +to round the double expression to an integer. The problem with this: + +1. It is unnecessarily slow. +2. It is incorrect. The number 0.499999975 (smallest representable float + number below 0.5) rounds to 1.0. Even worse behavior for negative + numbers where both -0.5f and -1.4f both round to 0.0. diff --git a/docs/clang-tidy/checks/misc-inefficient-algorithm.rst b/docs/clang-tidy/checks/misc-inefficient-algorithm.rst new file mode 100644 index 000000000..c61db68e5 --- /dev/null +++ b/docs/clang-tidy/checks/misc-inefficient-algorithm.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - misc-inefficient-algorithm + +misc-inefficient-algorithm +========================== + + +Warns on inefficient use of STL algorithms on associative containers. + +Associative containers implements some of the algorithms as methods which +should be preferred to the algorithms in the algorithm header. The methods +can take advanatage of the order of the elements. diff --git a/docs/clang-tidy/checks/misc-macro-parentheses.rst b/docs/clang-tidy/checks/misc-macro-parentheses.rst new file mode 100644 index 000000000..6120170be --- /dev/null +++ b/docs/clang-tidy/checks/misc-macro-parentheses.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-macro-parentheses + +misc-macro-parentheses +====================== + + +Finds macros that can have unexpected behaviour due to missing parentheses. + +Macros are expanded by the preprocessor as-is. As a result, there can be +unexpected behaviour; operators may be evaluated in unexpected order and +unary operators may become binary operators, etc. + +When the replacement list has an expression, it is recommended to surround +it with parentheses. This ensures that the macro result is evaluated +completely before it is used. + +It is also recommended to surround macro arguments in the replacement list +with parentheses. This ensures that the argument value is calculated +properly. diff --git a/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst b/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst new file mode 100644 index 000000000..7cd3781ee --- /dev/null +++ b/docs/clang-tidy/checks/misc-macro-repeated-side-effects.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-macro-repeated-side-effects + +misc-macro-repeated-side-effects +================================ + + +Checks for repeated argument with side effects in macros. diff --git a/docs/clang-tidy/checks/misc-misplaced-const.rst b/docs/clang-tidy/checks/misc-misplaced-const.rst new file mode 100644 index 000000000..ee1549f11 --- /dev/null +++ b/docs/clang-tidy/checks/misc-misplaced-const.rst @@ -0,0 +1,22 @@ +.. title:: clang-tidy - misc-misplaced-const + +misc-misplaced-const +==================== + +This check diagnoses when a ``const`` qualifier is applied to a ``typedef`` to a +pointer type rather than to the pointee, because such constructs are often +misleading to developers because the ``const`` applies to the pointer rather +than the pointee. + +For instance, in the following code, the resulting type is ``int *`` ``const`` +rather than ``const int *``: + +.. code-block:: c++ + + typedef int *int_ptr; + void f(const int_ptr ptr); + +The check does not diagnose when the underlying ``typedef`` type is a pointer to +a ``const`` type or a function pointer type. This is because the ``const`` +qualifier is less likely to be mistaken because it would be redundant (or +disallowed) on the underlying pointee type. diff --git a/docs/clang-tidy/checks/misc-misplaced-widening-cast.rst b/docs/clang-tidy/checks/misc-misplaced-widening-cast.rst new file mode 100644 index 000000000..a2fc2bfdf --- /dev/null +++ b/docs/clang-tidy/checks/misc-misplaced-widening-cast.rst @@ -0,0 +1,65 @@ +.. title:: clang-tidy - misc-misplaced-widening-cast + +misc-misplaced-widening-cast +============================ + +This check will warn when there is a cast of a calculation result to a bigger +type. If the intention of the cast is to avoid loss of precision then the cast +is misplaced, and there can be loss of precision. Otherwise the cast is +ineffective. + +Example code: + +.. code-block:: c++ + + long f(int x) { + return (long)(x * 1000); + } + +The result ``x * 1000`` is first calculated using ``int`` precision. If the +result exceeds ``int`` precision there is loss of precision. Then the result is +casted to ``long``. + +If there is no loss of precision then the cast can be removed or you can +explicitly cast to ``int`` instead. + +If you want to avoid loss of precision then put the cast in a proper location, +for instance: + +.. code-block:: c++ + + long f(int x) { + return (long)x * 1000; + } + +Implicit casts +-------------- + +Forgetting to place the cast at all is at least as dangerous and at least as +common as misplacing it. If :option:`CheckImplicitCasts` is enabled the check +also detects these cases, for instance: + +.. code-block:: c++ + + long f(int x) { + return x * 1000; + } + +Floating point +-------------- + +Currently warnings are only written for integer conversion. No warning is +written for this code: + +.. code-block:: c++ + + double f(float x) { + return (double)(x * 10.0f); + } + +Options +------- + +.. option:: CheckImplicitCasts + + If non-zero, enables detection of implicit casts. Default is non-zero. diff --git a/docs/clang-tidy/checks/misc-move-const-arg.rst b/docs/clang-tidy/checks/misc-move-const-arg.rst new file mode 100644 index 000000000..00f475334 --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-const-arg.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - misc-move-const-arg + +misc-move-const-arg +=================== + +The check warns + +- if ``std::move()`` is called with a constant argument, + +- if ``std::move()`` is called with an argument of a trivially-copyable type, + +- if the result of ``std::move()`` is passed as a const reference argument. + +In all three cases, the check will suggest a fix that removes the +``std::move()``. + +Here are examples of each of the three cases: + +.. code-block:: c++ + + const string s; + return std::move(s); // Warning: std::move of the const variable has no effect + + int x; + return std::move(x); // Warning: std::move of the variable of a trivially-copyable type has no effect + + void f(const string &s); + string s; + f(std::move(s)); // Warning: passing result of std::move as a const reference argument; no move will actually happen diff --git a/docs/clang-tidy/checks/misc-move-constructor-init.rst b/docs/clang-tidy/checks/misc-move-constructor-init.rst new file mode 100644 index 000000000..f56c592bc --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-constructor-init.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - misc-move-constructor-init + +misc-move-constructor-init +========================== + +"cert-oop11-cpp" redirects here as an alias for this check. + +The check flags user-defined move constructors that have a ctor-initializer +initializing a member or base class through a copy constructor instead of a +move constructor. + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/misc-move-forwarding-reference.rst b/docs/clang-tidy/checks/misc-move-forwarding-reference.rst new file mode 100644 index 000000000..327f72053 --- /dev/null +++ b/docs/clang-tidy/checks/misc-move-forwarding-reference.rst @@ -0,0 +1,60 @@ +.. title:: clang-tidy - misc-move-forwarding-reference + +misc-move-forwarding-reference +============================== + +Warns if ``std::move`` is called on a forwarding reference, for example: + + .. code-block:: c++ + + template + void foo(T&& t) { + bar(std::move(t)); + } + +`Forwarding references +`_ should +typically be passed to ``std::forward`` instead of ``std::move``, and this is +the fix that will be suggested. + +(A forwarding reference is an rvalue reference of a type that is a deduced +function template argument.) + +In this example, the suggested fix would be + + .. code-block:: c++ + + bar(std::forward(t)); + +Background +---------- + +Code like the example above is sometimes written with the expectation that +``T&&`` will always end up being an rvalue reference, no matter what type is +deduced for ``T``, and that it is therefore not possible to pass an lvalue to +``foo()``. However, this is not true. Consider this example: + + .. code-block:: c++ + + std::string s = "Hello, world"; + foo(s); + +This code compiles and, after the call to ``foo()``, ``s`` is left in an +indeterminate state because it has been moved from. This may be surprising to +the caller of ``foo()`` because no ``std::move`` was used when calling +``foo()``. + +The reason for this behavior lies in the special rule for template argument +deduction on function templates like ``foo()`` -- i.e. on function templates +that take an rvalue reference argument of a type that is a deduced function +template argument. (See section [temp.deduct.call]/3 in the C++11 standard.) + +If ``foo()`` is called on an lvalue (as in the example above), then ``T`` is +deduced to be an lvalue reference. In the example, ``T`` is deduced to be +``std::string &``. The type of the argument ``t`` therefore becomes +``std::string& &&``; by the reference collapsing rules, this collapses to +``std::string&``. + +This means that the ``foo(s)`` call passes ``s`` as an lvalue reference, and +``foo()`` ends up moving ``s`` and thereby placing it into an indeterminate +state. diff --git a/docs/clang-tidy/checks/misc-multiple-statement-macro.rst b/docs/clang-tidy/checks/misc-multiple-statement-macro.rst new file mode 100644 index 000000000..a0a65946f --- /dev/null +++ b/docs/clang-tidy/checks/misc-multiple-statement-macro.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-multiple-statement-macro + +misc-multiple-statement-macro +============================= + +Detect multiple statement macros that are used in unbraced conditionals. Only +the first statement of the macro will be inside the conditional and the other +ones will be executed unconditionally. + +Example: + +.. code-block:: c++ + + #define INCREMENT_TWO(x, y) (x)++; (y)++ + if (do_increment) + INCREMENT_TWO(a, b); // (b)++ will be executed unconditionally. diff --git a/docs/clang-tidy/checks/misc-new-delete-overloads.rst b/docs/clang-tidy/checks/misc-new-delete-overloads.rst new file mode 100644 index 000000000..727436d84 --- /dev/null +++ b/docs/clang-tidy/checks/misc-new-delete-overloads.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - misc-new-delete-overloads + +misc-new-delete-overloads +========================= + +`cert-dcl54-cpp` redirects here as an alias for this check. + +The check flags overloaded operator ``new()`` and operator ``delete()`` +functions that do not have a corresponding free store function defined within +the same scope. +For instance, the check will flag a class implementation of a non-placement +operator ``new()`` when the class does not also define a non-placement operator +``delete()`` function as well. + +The check does not flag implicitly-defined operators, deleted or private +operators, or placement operators. + +This check corresponds to CERT C++ Coding Standard rule `DCL54-CPP. Overload allocation and deallocation functions as a pair in the same scope +`_. diff --git a/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst b/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst new file mode 100644 index 000000000..9d3d4f79a --- /dev/null +++ b/docs/clang-tidy/checks/misc-noexcept-move-constructor.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-noexcept-move-constructor + +misc-noexcept-move-constructor +============================== + + +The check flags user-defined move constructors and assignment operators not +marked with ``noexcept`` or marked with ``noexcept(expr)`` where ``expr`` +evaluates to ``false`` (but is not a ``false`` literal itself). + +Move constructors of all the types used with STL containers, for example, +need to be declared ``noexcept``. Otherwise STL will choose copy constructors +instead. The same is valid for move assignment operations. diff --git a/docs/clang-tidy/checks/misc-non-copyable-objects.rst b/docs/clang-tidy/checks/misc-non-copyable-objects.rst new file mode 100644 index 000000000..d1f7bba39 --- /dev/null +++ b/docs/clang-tidy/checks/misc-non-copyable-objects.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-non-copyable-objects + +misc-non-copyable-objects +========================= + +`cert-fio38-c` redirects here as an alias for this check. + +The check flags dereferences and non-pointer declarations of objects that are +not meant to be passed by value, such as C FILE objects or POSIX +``pthread_mutex_t`` objects. + +This check corresponds to CERT C++ Coding Standard rule `FIO38-C. Do not copy a FILE object +`_. diff --git a/docs/clang-tidy/checks/misc-redundant-expression.rst b/docs/clang-tidy/checks/misc-redundant-expression.rst new file mode 100644 index 000000000..ddef9af9b --- /dev/null +++ b/docs/clang-tidy/checks/misc-redundant-expression.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - misc-redundant-expression + +misc-redundant-expression +========================= + +Detect redundant expressions which are typically errors due to copy-paste. + +Depending on the operator expressions may be + +- redundant, + +- always be ``true``, + +- always be ``false``, + +- always be a constant (zero or one). + +Example: + +.. code-block:: c++ + + ((x+1) | (x+1)) // (x+1) is redundant + (p->x == p->x) // always true + (p->x < p->x) // always false + (speed - speed + 1 == 12) // speed - speed is always zero diff --git a/docs/clang-tidy/checks/misc-sizeof-container.rst b/docs/clang-tidy/checks/misc-sizeof-container.rst new file mode 100644 index 000000000..9ee444066 --- /dev/null +++ b/docs/clang-tidy/checks/misc-sizeof-container.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - misc-sizeof-container + +misc-sizeof-container +===================== + +The check finds usages of ``sizeof`` on expressions of STL container types. Most +likely the user wanted to use ``.size()`` instead. + +All class/struct types declared in namespace ``std::`` having a const ``size()`` +method are considered containers, with the exception of ``std::bitset`` and +``std::array``. + +Examples: + +.. code-block:: c++ + + std::string s; + int a = 47 + sizeof(s); // warning: sizeof() doesn't return the size of the container. Did you mean .size()? + + int b = sizeof(std::string); // no warning, probably intended. + + std::string array_of_strings[10]; + int c = sizeof(array_of_strings) / sizeof(array_of_strings[0]); // no warning, definitely intended. + + std::array std_array; + int d = sizeof(std_array); // no warning, probably intended. diff --git a/docs/clang-tidy/checks/misc-sizeof-expression.rst b/docs/clang-tidy/checks/misc-sizeof-expression.rst new file mode 100644 index 000000000..d3e379002 --- /dev/null +++ b/docs/clang-tidy/checks/misc-sizeof-expression.rst @@ -0,0 +1,154 @@ +.. title:: clang-tidy - misc-sizeof-expression + +misc-sizeof-expression +====================== + +The check finds usages of ``sizeof`` expressions which are most likely errors. + +The ``sizeof`` operator yields the size (in bytes) of its operand, which may be +an expression or the parenthesized name of a type. Misuse of this operator may +be leading to errors and possible software vulnerabilities. + +Suspicious usage of 'sizeof(K)' +------------------------------- + +A common mistake is to query the ``sizeof`` of an integer literal. This is +equivalent to query the size of its type (probably ``int``). The intent of the +programmer was probably to simply get the integer and not its size. + +.. code-block:: c++ + + #define BUFLEN 42 + char buf[BUFLEN]; + memset(buf, 0, sizeof(BUFLEN)); // sizeof(42) ==> sizeof(int) + +Suspicious usage of 'sizeof(this)' +---------------------------------- + +The ``this`` keyword is evaluated to a pointer to an object of a given type. +The expression ``sizeof(this)`` is returning the size of a pointer. The +programmer most likely wanted the size of the object and not the size of the +pointer. + +.. code-block:: c++ + + class Point { + [...] + size_t size() { return sizeof(this); } // should probably be sizeof(*this) + [...] + }; + +Suspicious usage of 'sizeof(char*)' +----------------------------------- + +There is a subtle difference between declaring a string literal with +``char* A = ""`` and ``char A[] = ""``. The first case has the type ``char*`` +instead of the aggregate type ``char[]``. Using ``sizeof`` on an object declared +with ``char*`` type is returning the size of a pointer instead of the number of +characters (bytes) in the string literal. + +.. code-block:: c++ + + const char* kMessage = "Hello World!"; // const char kMessage[] = "..."; + void getMessage(char* buf) { + memcpy(buf, kMessage, sizeof(kMessage)); // sizeof(char*) + } + +Suspicious usage of 'sizeof(A*)' +-------------------------------- + +A common mistake is to compute the size of a pointer instead of its pointee. +These cases may occur because of explicit cast or implicit conversion. + +.. code-block:: c++ + + int A[10]; + memset(A, 0, sizeof(A + 0)); + + struct Point point; + memset(point, 0, sizeof(&point)); + +Suspicious usage of 'sizeof(...)/sizeof(...)' +--------------------------------------------- + +Dividing ``sizeof`` expressions is typically used to retrieve the number of +elements of an aggregate. This check warns on incompatible or suspicious cases. + +In the following example, the entity has 10-bytes and is incompatible with the +type ``int`` which has 4 bytes. + +.. code-block:: c++ + + char buf[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // sizeof(buf) => 10 + void getMessage(char* dst) { + memcpy(dst, buf, sizeof(buf) / sizeof(int)); // sizeof(int) => 4 [incompatible sizes] + } + +In the following example, the expression ``sizeof(Values)`` is returning the +size of ``char*``. One can easily be fooled by its declaration, but in parameter +declaration the size '10' is ignored and the function is receiving a ``char*``. + +.. code-block:: c++ + + char OrderedValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + return CompareArray(char Values[10]) { + return memcmp(OrderedValues, Values, sizeof(Values)) == 0; // sizeof(Values) ==> sizeof(char*) [implicit cast to char*] + } + +Suspicious 'sizeof' by 'sizeof' expression +------------------------------------------ + +Multiplying ``sizeof`` expressions typically makes no sense and is probably a +logic error. In the following example, the programmer used ``*`` instead of +``/``. + +.. code-block:: c++ + + const char kMessage[] = "Hello World!"; + void getMessage(char* buf) { + memcpy(buf, kMessage, sizeof(kMessage) * sizeof(char)); // sizeof(kMessage) / sizeof(char) + } + +This check may trigger on code using the arraysize macro. The following code is +working correctly but should be simplified by using only the ``sizeof`` +operator. + +.. code-block:: c++ + + extern Object objects[100]; + void InitializeObjects() { + memset(objects, 0, arraysize(objects) * sizeof(Object)); // sizeof(objects) + } + +Suspicious usage of 'sizeof(sizeof(...))' +----------------------------------------- + +Getting the ``sizeof`` of a ``sizeof`` makes no sense and is typically an error +hidden through macros. + +.. code-block:: c++ + + #define INT_SZ sizeof(int) + int buf[] = { 42 }; + void getInt(int* dst) { + memcpy(dst, buf, sizeof(INT_SZ)); // sizeof(sizeof(int)) is suspicious. + } + +Options +------- + +.. option:: WarnOnSizeOfConstant + + When non-zero, the check will warn on an expression like + ``sizeof(CONSTANT)``. Default is `1`. + +.. option:: WarnOnSizeOfThis + + When non-zero, the check will warn on an expression like ``sizeof(this)``. + Default is `1`. + +.. option:: WarnOnSizeOfCompareToConstant + + When non-zero, the check will warn on an expression like + ``sizeof(epxr) <= k`` for a suspicious constant `k` while `k` is `0` or + greater than `0x8000`. Default is `1`. diff --git a/docs/clang-tidy/checks/misc-static-assert.rst b/docs/clang-tidy/checks/misc-static-assert.rst new file mode 100644 index 000000000..cf0cc2d13 --- /dev/null +++ b/docs/clang-tidy/checks/misc-static-assert.rst @@ -0,0 +1,12 @@ +.. title:: clang-tidy - misc-static-assert + +misc-static-assert +================== + +`cert-dcl03-c` redirects here as an alias for this check. + +Replaces ``assert()`` with ``static_assert()`` if the condition is evaluatable +at compile time. + +The condition of ``static_assert()`` is evaluated at compile time which is +safer and more efficient. diff --git a/docs/clang-tidy/checks/misc-string-compare.rst b/docs/clang-tidy/checks/misc-string-compare.rst new file mode 100644 index 000000000..78a5d4f65 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-compare.rst @@ -0,0 +1,54 @@ +.. title:: clang-tidy - misc-string-compare + +misc-string-compare +=================== + +Finds string comparisons using the compare method. + +A common mistake is to use the string's ``compare`` method instead of using the +equality or inequality operators. The compare method is intended for sorting +functions and thus returns a negative number, a positive number or +zero depending on the lexicographical relationship between the strings compared. +If an equality or inequality check can suffice, that is recommended. This is +recommended to avoid the risk of incorrect interpretation of the return value +and to simplify the code. The string equality and inequality operators can +also be faster than the ``compare`` method due to early termination. + +Examples: + +.. code-block:: c++ + + std::string str1{"a"}; + std::string str2{"b"}; + + // use str1 != str2 instead. + if (str1.compare(str2)) { + } + + // use str1 == str2 instead. + if (!str1.compare(str2)) { + } + + // use str1 == str2 instead. + if (str1.compare(str2) == 0) { + } + + // use str1 != str2 instead. + if (str1.compare(str2) != 0) { + } + + // use str1 == str2 instead. + if (0 == str1.compare(str2)) { + } + + // use str1 != str2 instead. + if (0 != str1.compare(str2)) { + } + + // Use str1 == "foo" instead. + if (str1.compare("foo") == 0) { + } + +The above code examples shows the list of if-statements that this check will +give a warning for. All of them uses ``compare`` to check if equality or +inequality of two strings instead of using the correct operators. diff --git a/docs/clang-tidy/checks/misc-string-constructor.rst b/docs/clang-tidy/checks/misc-string-constructor.rst new file mode 100644 index 000000000..a5d2c8844 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-constructor.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - misc-string-constructor + +misc-string-constructor +======================= + +Finds string constructors that are suspicious and probably errors. + +A common mistake is to swap parameters to the 'fill' string-constructor. + +Examples: + +.. code-block:: c++ + + std::string('x', 50) str; // should be std::string(50, 'x') + +Calling the string-literal constructor with a length bigger than the literal is +suspicious and adds extra random characters to the string. + +Examples: + +.. code-block:: c++ + + std::string("test", 200); // Will include random characters after "test". + +Creating an empty string from constructors with parameters is considered +suspicious. The programmer should use the empty constructor instead. + +Examples: + +.. code-block:: c++ + + std::string("test", 0); // Creation of an empty string. + +Options +------- + +.. option:: WarnOnLargeLength + + When non-zero, the check will warn on a string with a length greater than + `LargeLengthThreshold`. Default is `1`. + +.. option:: LargeLengthThreshold + + An integer specifying the large length threshold. Default is `0x800000`. diff --git a/docs/clang-tidy/checks/misc-string-integer-assignment.rst b/docs/clang-tidy/checks/misc-string-integer-assignment.rst new file mode 100644 index 000000000..d882e8d0d --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-integer-assignment.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - misc-string-integer-assignment + +misc-string-integer-assignment +============================== + +The check finds assignments of an integer to ``std::basic_string`` +(``std::string``, ``std::wstring``, etc.). The source of the problem is the +following assignment operator of ``std::basic_string``: + +.. code-block:: c++ + + basic_string& operator=( CharT ch ); + +Numeric types can be implicitly casted to character types. + +.. code-block:: c++ + + std::string s; + int x = 5965; + s = 6; + s = x; + +Use the appropriate conversion functions or character literals. + +.. code-block:: c++ + + std::string s; + int x = 5965; + s = '6'; + s = std::to_string(x); + +In order to suppress false positives, use an explicit cast. + +.. code-block:: c++ + + std::string s; + s = static_cast(6); diff --git a/docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst b/docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst new file mode 100644 index 000000000..29af0a4c6 --- /dev/null +++ b/docs/clang-tidy/checks/misc-string-literal-with-embedded-nul.rst @@ -0,0 +1,36 @@ +.. title:: clang-tidy - misc-string-literal-with-embedded-nul + +misc-string-literal-with-embedded-nul +===================================== + +Finds occurrences of string literal with embedded NUL character and validates +their usage. + +Invalid escaping +---------------- + +Special characters can be escaped within a string literal by using their +hexadecimal encoding like ``\x42``. A common mistake is to escape them +like this ``\0x42`` where the ``\0`` stands for the NUL character. + +.. code-block:: c++ + + const char* Example[] = "Invalid character: \0x12 should be \x12"; + const char* Bytes[] = "\x03\0x02\0x01\0x00\0xFF\0xFF\0xFF"; + +Truncated literal +----------------- + +String-like classes can manipulate strings with embedded NUL as they are keeping +track of the bytes and the length. This is not the case for a ``char*`` +(NUL-terminated) string. + +A common mistake is to pass a string-literal with embedded NUL to a string +constructor expecting a NUL-terminated string. The bytes after the first NUL +character are truncated. + +.. code-block:: c++ + + std::string str("abc\0def"); // "def" is truncated + str += "\0"; // This statement is doing nothing + if (str == "\0abc") return; // This expression is always true diff --git a/docs/clang-tidy/checks/misc-suspicious-enum-usage.rst b/docs/clang-tidy/checks/misc-suspicious-enum-usage.rst new file mode 100644 index 000000000..0a5a48428 --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-enum-usage.rst @@ -0,0 +1,78 @@ +.. title:: clang-tidy - misc-suspicious-enum-usage + +misc-suspicious-enum-usage +========================== + +The checker detects various cases when an enum is probably misused (as a bitmask +). + +1. When "ADD" or "bitwise OR" is used between two enum which come from different + types and these types value ranges are not disjoint. + +The following cases will be investigated only using :option:`StrictMode`. We +regard the enum as a (suspicious) +bitmask if the three conditions below are true at the same time: + +* at most half of the elements of the enum are non pow-of-2 numbers (because of + short enumerations) +* there is another non pow-of-2 number than the enum constant representing all + choices (the result "bitwise OR" operation of all enum elements) +* enum type variable/enumconstant is used as an argument of a `+` or "bitwise OR + " operator + +So whenever the non pow-of-2 element is used as a bitmask element we diagnose a +misuse and give a warning. + +2. Investigating the right hand side of `+=` and `|=` operator. +3. Check only the enum value side of a `|` and `+` operator if one of them is not + enum val. +4. Check both side of `|` or `+` operator where the enum values are from the + same enum type. + +Examples: + +.. code-block:: c++ + + enum { A, B, C }; + enum { D, E, F = 5 }; + enum { G = 10, H = 11, I = 12 }; + + unsigned flag; + flag = + A | + H; // OK, disjoint value intervalls in the enum types ->probably good use. + flag = B | F; // Warning, have common values so they are probably misused. + + // Case 2: + enum Bitmask { + A = 0, + B = 1, + C = 2, + D = 4, + E = 8, + F = 16, + G = 31 // OK, real bitmask. + }; + + enum Almostbitmask { + AA = 0, + BB = 1, + CC = 2, + DD = 4, + EE = 8, + FF = 16, + GG // Problem, forgot to initialize. + }; + + unsigned flag = 0; + flag |= E; // OK. + flag |= + EE; // Warning at the decl, and note that it was used here as a bitmask. + +Options +------- +.. option:: StrictMode + + Default value: 0. + When non-null the suspicious bitmask usage will be investigated additionally + to the different enum usage check. diff --git a/docs/clang-tidy/checks/misc-suspicious-missing-comma.rst b/docs/clang-tidy/checks/misc-suspicious-missing-comma.rst new file mode 100644 index 000000000..291f27ea2 --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-missing-comma.rst @@ -0,0 +1,59 @@ +.. title:: clang-tidy - misc-suspicious-missing-comma + +misc-suspicious-missing-comma +============================= + +String literals placed side-by-side are concatenated at translation phase 6 +(after the preprocessor). This feature is used to represent long string +literal on multiple lines. + +For instance, the following declarations are equivalent: + +.. code-block:: c++ + + const char* A[] = "This is a test"; + const char* B[] = "This" " is a " "test"; + +A common mistake done by programmers is to forget a comma between two string +literals in an array initializer list. + +.. code-block:: c++ + + const char* Test[] = { + "line 1", + "line 2" // Missing comma! + "line 3", + "line 4", + "line 5" + }; + +The array contains the string "line 2line3" at offset 1 (i.e. Test[1]). Clang +won't generate warnings at compile time. + +This check may warn incorrectly on cases like: + +.. code-block:: c++ + + const char* SupportedFormat[] = { + "Error %s", + "Code " PRIu64, // May warn here. + "Warning %s", + }; + +Options +------- + +.. option:: SizeThreshold + + An unsigned integer specifying the minimum size of a string literal to be + considered by the check. Default is `5U`. + +.. option:: RatioThreshold + + A string specifying the maximum threshold ratio [0, 1.0] of suspicious string + literals to be considered. Default is `".2"`. + +.. option:: MaxConcatenatedTokens + + An unsigned integer specifying the maximum number of concatenated tokens. + Default is `5U`. diff --git a/docs/clang-tidy/checks/misc-suspicious-semicolon.rst b/docs/clang-tidy/checks/misc-suspicious-semicolon.rst new file mode 100644 index 000000000..d38e6a685 --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-semicolon.rst @@ -0,0 +1,72 @@ +.. title:: clang-tidy - misc-suspicious-semicolon + +misc-suspicious-semicolon +========================= + +Finds most instances of stray semicolons that unexpectedly alter the meaning of +the code. More specifically, it looks for ``if``, ``while``, ``for`` and +``for-range`` statements whose body is a single semicolon, and then analyzes the +context of the code (e.g. indentation) in an attempt to determine whether that +is intentional. + + .. code-block:: c++ + + if (x < y); + { + x++; + } + +Here the body of the ``if`` statement consists of only the semicolon at the end +of the first line, and `x` will be incremented regardless of the condition. + + + .. code-block:: c++ + + while ((line = readLine(file)) != NULL); + processLine(line); + +As a result of this code, `processLine()` will only be called once, when the +``while`` loop with the empty body exits with `line == NULL`. The indentation of +the code indicates the intention of the programmer. + + + .. code-block:: c++ + + if (x >= y); + x -= y; + +While the indentation does not imply any nesting, there is simply no valid +reason to have an `if` statement with an empty body (but it can make sense for +a loop). So this check issues a warning for the code above. + +To solve the issue remove the stray semicolon or in case the empty body is +intentional, reflect this using code indentation or put the semicolon in a new +line. For example: + + .. code-block:: c++ + + while (readWhitespace()); + Token t = readNextToken(); + +Here the second line is indented in a way that suggests that it is meant to be +the body of the `while` loop - whose body is in fact empty, because of the +semicolon at the end of the first line. + +Either remove the indentation from the second line: + + .. code-block:: c++ + + while (readWhitespace()); + Token t = readNextToken(); + +... or move the semicolon from the end of the first line to a new line: + + .. code-block:: c++ + + while (readWhitespace()) + ; + + Token t = readNextToken(); + +In this case the check will assume that you know what you are doing, and will +not raise a warning. diff --git a/docs/clang-tidy/checks/misc-suspicious-string-compare.rst b/docs/clang-tidy/checks/misc-suspicious-string-compare.rst new file mode 100644 index 000000000..9918dee2f --- /dev/null +++ b/docs/clang-tidy/checks/misc-suspicious-string-compare.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - misc-suspicious-string-compare + +misc-suspicious-string-compare +============================== + +Find suspicious usage of runtime string comparison functions. +This check is valid in C and C++. + +Checks for calls with implicit comparator and proposed to explicitly add it. + +.. code-block:: c++ + + if (strcmp(...)) // Implicitly compare to zero + if (!strcmp(...)) // Won't warn + if (strcmp(...) != 0) // Won't warn + +Checks that compare function results (i,e, ``strcmp``) are compared to valid +constant. The resulting value is + +.. code:: + + < 0 when lower than, + > 0 when greater than, + == 0 when equals. + +A common mistake is to compare the result to `1` or `-1`. + +.. code-block:: c++ + + if (strcmp(...) == -1) // Incorrect usage of the returned value. + +Additionally, the check warns if the results value is implicitly cast to a +*suspicious* non-integer type. It's happening when the returned value is used in +a wrong context. + +.. code-block:: c++ + + if (strcmp(...) < 0.) // Incorrect usage of the returned value. + +Options +------- + +.. option:: WarnOnImplicitComparison + + When non-zero, the check will warn on implicit comparison. `1` by default. + +.. option:: WarnOnLogicalNotComparison + + When non-zero, the check will warn on logical not comparison. `0` by default. + +.. option:: StringCompareLikeFunctions + + A string specifying the comma-separated names of the extra string comparison + functions. Default is an empty string. + The check will detect the following string comparison functions: + `__builtin_memcmp`, `__builtin_strcasecmp`, `__builtin_strcmp`, + `__builtin_strncasecmp`, `__builtin_strncmp`, `_mbscmp`, `_mbscmp_l`, + `_mbsicmp`, `_mbsicmp_l`, `_mbsnbcmp`, `_mbsnbcmp_l`, `_mbsnbicmp`, + `_mbsnbicmp_l`, `_mbsncmp`, `_mbsncmp_l`, `_mbsnicmp`, `_mbsnicmp_l`, + `_memicmp`, `_memicmp_l`, `_stricmp`, `_stricmp_l`, `_strnicmp`, + `_strnicmp_l`, `_wcsicmp`, `_wcsicmp_l`, `_wcsnicmp`, `_wcsnicmp_l`, + `lstrcmp`, `lstrcmpi`, `memcmp`, `memicmp`, `strcasecmp`, `strcmp`, + `strcmpi`, `stricmp`, `strncasecmp`, `strncmp`, `strnicmp`, `wcscasecmp`, + `wcscmp`, `wcsicmp`, `wcsncmp`, `wcsnicmp`, `wmemcmp`. diff --git a/docs/clang-tidy/checks/misc-swapped-arguments.rst b/docs/clang-tidy/checks/misc-swapped-arguments.rst new file mode 100644 index 000000000..67a604a13 --- /dev/null +++ b/docs/clang-tidy/checks/misc-swapped-arguments.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-swapped-arguments + +misc-swapped-arguments +====================== + + +Finds potentially swapped arguments by looking at implicit conversions. diff --git a/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst b/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst new file mode 100644 index 000000000..1a6d6b3f8 --- /dev/null +++ b/docs/clang-tidy/checks/misc-throw-by-value-catch-by-reference.rst @@ -0,0 +1,34 @@ +.. title:: clang-tidy - misc-throw-by-value-catch-by-reference + +misc-throw-by-value-catch-by-reference +====================================== + +"cert-err09-cpp" redirects here as an alias for this check. +"cert-err61-cpp" redirects here as an alias for this check. + +Finds violations of the rule "Throw by value, catch by reference" presented for +example in "C++ Coding Standards" by H. Sutter and A. Alexandrescu. + +Exceptions: + * Throwing string literals will not be flagged despite being a pointer. They + are not susceptible to slicing and the usage of string literals is idomatic. + * Catching character pointers (``char``, ``wchar_t``, unicode character types) + will not be flagged to allow catching sting literals. + * Moved named values will not be flagged as not throwing an anonymous + temporary. In this case we can be sure that the user knows that the object + can't be accessed outside catch blocks handling the error. + * Throwing function parameters will not be flagged as not throwing an + anonymous temporary. This allows helper functions for throwing. + * Re-throwing caught exception variables will not be flragged as not throwing + an anonymous temporary. Although this can usually be done by just writing + ``throw;`` it happens often enough in real code. + +Options +------- + +.. option:: CheckThrowTemporaries + + Triggers detection of violations of the rule `Throw anonymous temporaries + `_. + Default is `1`. + diff --git a/docs/clang-tidy/checks/misc-unconventional-assign-operator.rst b/docs/clang-tidy/checks/misc-unconventional-assign-operator.rst new file mode 100644 index 000000000..8b85332fa --- /dev/null +++ b/docs/clang-tidy/checks/misc-unconventional-assign-operator.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-unconventional-assign-operator + +misc-unconventional-assign-operator +=================================== + + +Finds declarations of assign operators with the wrong return and/or argument +types and definitions with good return type but wrong ``return`` statements. + + * The return type must be ``Class&``. + * Works with move-assign and assign by value. + * Private and deleted operators are ignored. + * The operator must always return ``*this``. diff --git a/docs/clang-tidy/checks/misc-undelegated-constructor.rst b/docs/clang-tidy/checks/misc-undelegated-constructor.rst new file mode 100644 index 000000000..3b7fffcd2 --- /dev/null +++ b/docs/clang-tidy/checks/misc-undelegated-constructor.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - misc-undelegated-constructor + +misc-undelegated-constructor +============================ + + +Finds creation of temporary objects in constructors that look like a +function call to another constructor of the same class. + +The user most likely meant to use a delegating constructor or base class +initializer. diff --git a/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst b/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst new file mode 100644 index 000000000..858fbe78f --- /dev/null +++ b/docs/clang-tidy/checks/misc-uniqueptr-reset-release.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - misc-uniqueptr-reset-release + +misc-uniqueptr-reset-release +============================ + +Find and replace ``unique_ptr::reset(release())`` with ``std::move()``. + +Example: + +.. code-block:: c++ + + std::unique_ptr x, y; + x.reset(y.release()); -> x = std::move(y); + +If ``y`` is already rvalue, ``std::move()`` is not added. ``x`` and ``y`` can +also be ``std::unique_ptr*``. diff --git a/docs/clang-tidy/checks/misc-unused-alias-decls.rst b/docs/clang-tidy/checks/misc-unused-alias-decls.rst new file mode 100644 index 000000000..d0e8c7188 --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-alias-decls.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-unused-alias-decls + +misc-unused-alias-decls +======================= + + +Finds unused namespace alias declarations. diff --git a/docs/clang-tidy/checks/misc-unused-parameters.rst b/docs/clang-tidy/checks/misc-unused-parameters.rst new file mode 100644 index 000000000..4e037351b --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-parameters.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - misc-unused-parameters + +misc-unused-parameters +====================== + +Finds unused parameters and fixes them, so that `-Wunused-parameter` can be +turned on. diff --git a/docs/clang-tidy/checks/misc-unused-raii.rst b/docs/clang-tidy/checks/misc-unused-raii.rst new file mode 100644 index 000000000..d081702b3 --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-raii.rst @@ -0,0 +1,30 @@ +.. title:: clang-tidy - misc-unused-raii + +misc-unused-raii +================ + +Finds temporaries that look like RAII objects. + +The canonical example for this is a scoped lock. + +.. code-block:: c++ + + { + scoped_lock(&global_mutex); + critical_section(); + } + +The destructor of the scoped_lock is called before the ``critical_section`` is +entered, leaving it unprotected. + +We apply a number of heuristics to reduce the false positive count of this +check: + +- Ignore code expanded from macros. Testing frameworks make heavy use of this. + +- Ignore types with trivial destructors. They are very unlikely to be RAII + objects and there's no difference when they are deleted. + +- Ignore objects at the end of a compound statement (doesn't change behavior). + +- Ignore objects returned from a call. diff --git a/docs/clang-tidy/checks/misc-unused-using-decls.rst b/docs/clang-tidy/checks/misc-unused-using-decls.rst new file mode 100644 index 000000000..9de1cb2cc --- /dev/null +++ b/docs/clang-tidy/checks/misc-unused-using-decls.rst @@ -0,0 +1,13 @@ +.. title:: clang-tidy - misc-unused-using-decls + +misc-unused-using-decls +======================= + +Finds unused ``using`` declarations. + +Example: + +.. code-block:: c++ + + namespace n { class C; } + using n::C; // Never actually used. diff --git a/docs/clang-tidy/checks/misc-use-after-move.rst b/docs/clang-tidy/checks/misc-use-after-move.rst new file mode 100644 index 000000000..e65860803 --- /dev/null +++ b/docs/clang-tidy/checks/misc-use-after-move.rst @@ -0,0 +1,203 @@ +.. title:: clang-tidy - misc-use-after-move + +misc-use-after-move +=================== + +Warns if an object is used after it has been moved, for example: + + .. code-block:: c++ + + std::string str = "Hello, world!\n"; + std::vector messages; + messages.emplace_back(std::move(str)); + std::cout << str; + +The last line will trigger a warning that ``str`` is used after it has been +moved. + +The check does not trigger a warning if the object is reinitialized after the +move and before the use. For example, no warning will be output for this code: + + .. code-block:: c++ + + messages.emplace_back(std::move(str)); + str = "Greetings, stranger!\n"; + std::cout << str; + +The check takes control flow into account. A warning is only emitted if the use +can be reached from the move. This means that the following code does not +produce a warning: + + .. code-block:: c++ + + if (condition) { + messages.emplace_back(std::move(str)); + } else { + std::cout << str; + } + +On the other hand, the following code does produce a warning: + + .. code-block:: c++ + + for (int i = 0; i < 10; ++i) { + std::cout << str; + messages.emplace_back(std::move(str)); + } + +(The use-after-move happens on the second iteration of the loop.) + +In some cases, the check may not be able to detect that two branches are +mutually exclusive. For example (assuming that ``i`` is an int): + + .. code-block:: c++ + + if (i == 1) { + messages.emplace_back(std::move(str)); + } + if (i == 2) { + std::cout << str; + } + +In this case, the check will erroneously produce a warning, even though it is +not possible for both the move and the use to be executed. + +An erroneous warning can be silenced by reinitializing the object after the +move: + + .. code-block:: c++ + + if (i == 1) { + messages.emplace_back(std::move(str)); + str = ""; + } + if (i == 2) { + std::cout << str; + } + +Subsections below explain more precisely what exactly the check considers to be +a move, use, and reinitialization. + +Unsequenced moves, uses, and reinitializations +---------------------------------------------- + +In many cases, C++ does not make any guarantees about the order in which +sub-expressions of a statement are evaluated. This means that in code like the +following, it is not guaranteed whether the use will happen before or after the +move: + + .. code-block:: c++ + + void f(int i, std::vector v); + std::vector v = { 1, 2, 3 }; + f(v[1], std::move(v)); + +In this kind of situation, the check will note that the use and move are +unsequenced. + +The check will also take sequencing rules into account when reinitializations +occur in the same statement as moves or uses. A reinitialization is only +considered to reinitialize a variable if it is guaranteed to be evaluated after +the move and before the use. + +Move +---- + +The check currently only considers calls of ``std::move`` on local variables or +function parameters. It does not check moves of member variables or global +variables. + +Any call of ``std::move`` on a variable is considered to cause a move of that +variable, even if the result of ``std::move`` is not passed to an rvalue +reference parameter. + +This means that the check will flag a use-after-move even on a type that does +not define a move constructor or move assignment operator. This is intentional. +Developers may use ``std::move`` on such a type in the expectation that the type +will add move semantics in the future. If such a ``std::move`` has the potential +to cause a use-after-move, we want to warn about it even if the type does not +implement move semantics yet. + +Furthermore, if the result of ``std::move`` *is* passed to an rvalue reference +parameter, this will always be considered to cause a move, even if the function +that consumes this parameter does not move from it, or if it does so only +conditionally. For example, in the following situation, the check will assume +that a move always takes place: + + .. code-block:: c++ + + std::vector messages; + void f(std::string &&str) { + // Only remember the message if it isn't empty. + if (!str.empty()) { + messages.emplace_back(std::move(str)); + } + } + std::string str = ""; + f(std::move(str)); + +The check will assume that the last line causes a move, even though, in this +particular case, it does not. Again, this is intentional. + +When analyzing the order in which moves, uses and reinitializations happen (see +section `Unsequenced moves, uses, and reinitializations`_), the move is assumed +to occur in whichever function the result of the ``std::move`` is passed to. + +Use +--- + +Any occurrence of the moved variable that is not a reinitialization (see below) +is considered to be a use. + +An exception to this are objects of type ``std::unique_ptr``, +``std::shared_ptr`` and ``std::weak_ptr``, which have defined move behavior +(objects of these classes are guaranteed to be empty after they have been moved +from). Therefore, an object of these classes will only be considered to be used +if it is dereferenced, i.e. if ``operator*``, ``operator->`` or ``operator[]`` +(in the case of ``std::unique_ptr``) is called on it. + +If multiple uses occur after a move, only the first of these is flagged. + +Reinitialization +---------------- + +The check considers a variable to be reinitialized in the following cases: + + - The variable occurs on the left-hand side of an assignment. + + - The variable is passed to a function as a non-const pointer or non-const + lvalue reference. (It is assumed that the variable may be an out-parameter + for the function.) + + - ``clear()`` or ``assign()`` is called on the variable and the variable is of + one of the standard container types ``basic_string``, ``vector``, ``deque``, + ``forward_list``, ``list``, ``set``, ``map``, ``multiset``, ``multimap``, + ``unordered_set``, ``unordered_map``, ``unordered_multiset``, + ``unordered_multimap``. + + - ``reset()`` is called on the variable and the variable is of type + ``std::unique_ptr``, ``std::shared_ptr`` or ``std::weak_ptr``. + +If the variable in question is a struct and an individual member variable of +that struct is written to, the check does not consider this to be a +reinitialization -- even if, eventually, all member variables of the struct are +written to. For example: + + .. code-block:: c++ + + struct S { + std::string str; + int i; + }; + S s = { "Hello, world!\n", 42 }; + S s_other = std::move(s); + s.str = "Lorem ipsum"; + s.i = 99; + +The check will not consider ``s`` to be reinitialized after the last line; +instead, the line that assigns to ``s.str`` will be flagged as a use-after-move. +This is intentional as this pattern of reinitializing a struct is error-prone. +For example, if an additional member variable is added to ``S``, it is easy to +forget to add the reinitialization for this additional member. Instead, it is +safer to assign to the entire struct in one go, and this will also avoid the +use-after-move warning. diff --git a/docs/clang-tidy/checks/misc-virtual-near-miss.rst b/docs/clang-tidy/checks/misc-virtual-near-miss.rst new file mode 100644 index 000000000..687198977 --- /dev/null +++ b/docs/clang-tidy/checks/misc-virtual-near-miss.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - misc-virtual-near-miss + +misc-virtual-near-miss +====================== + +Warn if a function is a near miss (ie. the name is very similar and the function +signiture is the same) to a virtual function from a base class. + +Example: + +.. code-block:: c++ + + struct Base { + virtual void func(); + }; + + struct Derived : Base { + virtual funk(); + // warning: 'Derived::funk' has a similar name and the same signature as virtual method 'Base::func'; did you mean to override it? + }; diff --git a/docs/clang-tidy/checks/modernize-avoid-bind.rst b/docs/clang-tidy/checks/modernize-avoid-bind.rst new file mode 100644 index 000000000..7ea9beca8 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-avoid-bind.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - modernize-avoid-bind + +modernize-avoid-bind +==================== + +The check finds uses of ``std::bind`` and replaces simple uses with lambdas. +Lambdas will use value-capture where required. + +Right now it only handles free functions, not member functions. + +Given: + +.. code-block:: c++ + + int add(int x, int y) { return x + y; } + +Then: + +.. code-block:: c++ + + void f() { + int x = 2; + auto clj = std::bind(add, x, _1); + } + +is replaced by: + +.. code-block:: c++ + + void f() { + int x = 2; + auto clj = [=](auto && arg1) { return add(x, arg1); }; + } + +``std::bind`` can be hard to read and can result in larger object files and +binaries due to type information that will not be produced by equivalent +lambdas. diff --git a/docs/clang-tidy/checks/modernize-deprecated-headers.rst b/docs/clang-tidy/checks/modernize-deprecated-headers.rst new file mode 100644 index 000000000..4c4750a3a --- /dev/null +++ b/docs/clang-tidy/checks/modernize-deprecated-headers.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - modernize-deprecated-headers + +modernize-deprecated-headers +============================ + +Some headers from C library were deprecated in C++ and are no longer welcome in +C++ codebases. Some have no effect in C++. For more details refer to the C++ 14 +Standard [depr.c.headers] section. + +This check replaces C standard library headers with their C++ alternatives and +removes redundant ones. + +Improtant note: the Standard doesn't guarantee that the C++ headers declare all +the same functions in the global namespace. The check in its current form can +break the code that uses library symbols from the global namespace. + +* `` +* `` +* `` +* `` +* `` // deprecated since C++11 +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` +* `` // deprecated since C++11 +* `` +* `` // deprecated since C++11 +* `` +* `` + +If the specified standard is older than C++11 the check will only replace +headers deprecated before C++11, otherwise -- every header that appeared in +the previous list. + +These headers don't have effect in C++: + +* `` +* `` +* `` diff --git a/docs/clang-tidy/checks/modernize-loop-convert.rst b/docs/clang-tidy/checks/modernize-loop-convert.rst new file mode 100644 index 000000000..bad574f6e --- /dev/null +++ b/docs/clang-tidy/checks/modernize-loop-convert.rst @@ -0,0 +1,255 @@ +.. title:: clang-tidy - modernize-loop-convert + +modernize-loop-convert +====================== + +This check converts ``for(...; ...; ...)`` loops to use the new range-based +loops in C++11. + +Three kinds of loops can be converted: + +- Loops over statically allocated arrays. +- Loops over containers, using iterators. +- Loops over array-like containers, using ``operator[]`` and ``at()``. + +MinConfidence option +-------------------- + +risky +^^^^^ + +In loops where the container expression is more complex than just a +reference to a declared expression (a variable, function, enum, etc.), +and some part of it appears elsewhere in the loop, we lower our confidence +in the transformation due to the increased risk of changing semantics. +Transformations for these loops are marked as `risky`, and thus will only +be converted if the minimum required confidence level is set to `risky`. + +.. code-block:: c++ + + int arr[10][20]; + int l = 5; + + for (int j = 0; j < 20; ++j) + int k = arr[l][j] + l; // using l outside arr[l] is considered risky + + for (int i = 0; i < obj.getVector().size(); ++i) + obj.foo(10); // using 'obj' is considered risky + +See +:ref:`Range-based loops evaluate end() only once` +for an example of an incorrect transformation when the minimum required confidence +level is set to `risky`. + +reasonable (Default) +^^^^^^^^^^^^^^^^^^^^ + +If a loop calls ``.end()`` or ``.size()`` after each iteration, the +transformation for that loop is marked as `reasonable`, and thus will +be converted if the required confidence level is set to `reasonable` +(default) or lower. + +.. code-block:: c++ + + // using size() is considered reasonable + for (int i = 0; i < container.size(); ++i) + cout << container[i]; + +safe +^^^^ + +Any other loops that do not match the above criteria to be marked as +`risky` or `reasonable` are marked `safe`, and thus will be converted +if the required confidence level is set to `safe` or lower. + +.. code-block:: c++ + + int arr[] = {1,2,3}; + + for (int i = 0; i < 3; ++i) + cout << arr[i]; + +Example +------- + +Original: + +.. code-block:: c++ + + const int N = 5; + int arr[] = {1,2,3,4,5}; + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + + // safe conversion + for (int i = 0; i < N; ++i) + cout << arr[i]; + + // reasonable conversion + for (vector::iterator it = v.begin(); it != v.end(); ++it) + cout << *it; + + // reasonable conversion + for (int i = 0; i < v.size(); ++i) + cout << v[i]; + +After applying the check with minimum confidence level set to `reasonable` (default): + +.. code-block:: c++ + + const int N = 5; + int arr[] = {1,2,3,4,5}; + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + + // safe conversion + for (auto & elem : arr) + cout << elem; + + // reasonable conversion + for (auto & elem : v) + cout << elem; + + // reasonable conversion + for (auto & elem : v) + cout << elem; + +Limitations +----------- + +There are certain situations where the tool may erroneously perform +transformations that remove information and change semantics. Users of the tool +should be aware of the behaviour and limitations of the check outlined by +the cases below. + +Comments inside loop headers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Comments inside the original loop header are ignored and deleted when +transformed. + +.. code-block:: c++ + + for (int i = 0; i < N; /* This will be deleted */ ++i) { } + +Range-based loops evaluate end() only once +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The C++11 range-based for loop calls ``.end()`` only once during the +initialization of the loop. If in the original loop ``.end()`` is called after +each iteration the semantics of the transformed loop may differ. + +.. code-block:: c++ + + // The following is semantically equivalent to the C++11 range-based for loop, + // therefore the semantics of the header will not change. + for (iterator it = container.begin(), e = container.end(); it != e; ++it) { } + + // Instead of calling .end() after each iteration, this loop will be + // transformed to call .end() only once during the initialization of the loop, + // which may affect semantics. + for (iterator it = container.begin(); it != container.end(); ++it) { } + +.. _IncorrectRiskyTransformation: + +As explained above, calling member functions of the container in the body +of the loop is considered `risky`. If the called member function modifies the +container the semantics of the converted loop will differ due to ``.end()`` +being called only once. + +.. code-block:: c++ + + bool flag = false; + for (vector::iterator it = vec.begin(); it != vec.end(); ++it) { + // Add a copy of the first element to the end of the vector. + if (!flag) { + // This line makes this transformation 'risky'. + vec.push_back(*it); + flag = true; + } + cout << *it; + } + +The original code above prints out the contents of the container including the +newly added element while the converted loop, shown below, will only print the +original contents and not the newly added element. + +.. code-block:: c++ + + bool flag = false; + for (auto & elem : vec) { + // Add a copy of the first element to the end of the vector. + if (!flag) { + // This line makes this transformation 'risky' + vec.push_back(elem); + flag = true; + } + cout << elem; + } + +Semantics will also be affected if ``.end()`` has side effects. For example, in +the case where calls to ``.end()`` are logged the semantics will change in the +transformed loop if ``.end()`` was originally called after each iteration. + +.. code-block:: c++ + + iterator end() { + num_of_end_calls++; + return container.end(); + } + +Overloaded operator->() with side effects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Similarly, if ``operator->()`` was overloaded to have side effects, such as +logging, the semantics will change. If the iterator's ``operator->()`` was used +in the original loop it will be replaced with ``.`` +instead due to the implicit dereference as part of the range-based for loop. +Therefore any side effect of the overloaded ``operator->()`` will no longer be +performed. + +.. code-block:: c++ + + for (iterator it = c.begin(); it != c.end(); ++it) { + it->func(); // Using operator->() + } + // Will be transformed to: + for (auto & elem : c) { + elem.func(); // No longer using operator->() + } + +Pointers and references to containers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While most of the check's risk analysis is dedicated to determining whether +the iterator or container was modified within the loop, it is possible to +circumvent the analysis by accessing and modifying the container through a +pointer or reference. + +If the container were directly used instead of using the pointer or reference +the following transformation would have only been applied at the `risky` +level since calling a member function of the container is considered `risky`. +The check cannot identify expressions associated with the container that are +different than the one used in the loop header, therefore the transformation +below ends up being performed at the `safe` level. + +.. code-block:: c++ + + vector vec; + + vector *ptr = &vec; + vector &ref = vec; + + for (vector::iterator it = vec.begin(), e = vec.end(); it != e; ++it) { + if (!flag) { + // Accessing and modifying the container is considered risky, but the risk + // level is not raised here. + ptr->push_back(*it); + ref.push_back(*it); + flag = true; + } + } diff --git a/docs/clang-tidy/checks/modernize-make-shared.rst b/docs/clang-tidy/checks/modernize-make-shared.rst new file mode 100644 index 000000000..d33d3b1b0 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-shared.rst @@ -0,0 +1,27 @@ +.. 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); 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..0dce5219f --- /dev/null +++ b/docs/clang-tidy/checks/modernize-make-unique.rst @@ -0,0 +1,27 @@ +.. 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); 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-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-use-auto.rst b/docs/clang-tidy/checks/modernize-use-auto.rst new file mode 100644 index 000000000..157b26cca --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-auto.rst @@ -0,0 +1,196 @@ +.. title:: clang-tidy - modernize-use-auto + +modernize-use-auto +================== + +This check is responsible for using the ``auto`` type specifier for variable +declarations to *improve code readability and maintainability*. For example: + +.. code-block:: c++ + + std::vector::iterator I = my_container.begin(); + + // transforms to: + + auto I = my_container.begin(); + +The ``auto`` type specifier will only be introduced in situations where the +variable type matches the type of the initializer expression. In other words +``auto`` should deduce the same type that was originally spelled in the source. +However, not every situation should be transformed: + +.. code-block:: c++ + + int val = 42; + InfoStruct &I = SomeObject.getInfo(); + + // Should not become: + + auto val = 42; + auto &I = SomeObject.getInfo(); + +In this example using ``auto`` for builtins doesn't improve readability. In +other situations it makes the code less self-documenting impairing readability +and maintainability. As a result, ``auto`` is used only introduced in specific +situations described below. + +Iterators +--------- + +Iterator type specifiers tend to be long and used frequently, especially in +loop constructs. Since the functions generating iterators have a common format, +the type specifier can be replaced without obscuring the meaning of code while +improving readability and maintainability. + +.. code-block:: c++ + + for (std::vector::iterator I = my_container.begin(), + E = my_container.end(); + I != E; ++I) { + } + + // becomes + + for (auto I = my_container.begin(), E = my_container.end(); I != E; ++I) { + } + +The check will only replace iterator type-specifiers when all of the following +conditions are satisfied: + +* The iterator is for one of the standard container in ``std`` namespace: + + * ``array`` + * ``deque`` + * ``forward_list`` + * ``list`` + * ``vector`` + * ``map`` + * ``multimap`` + * ``set`` + * ``multiset`` + * ``unordered_map`` + * ``unordered_multimap`` + * ``unordered_set`` + * ``unordered_multiset`` + * ``queue`` + * ``priority_queue`` + * ``stack`` + +* The iterator is one of the possible iterator types for standard containers: + + * ``iterator`` + * ``reverse_iterator`` + * ``const_iterator`` + * ``const_reverse_iterator`` + +* In addition to using iterator types directly, typedefs or other ways of + referring to those types are also allowed. However, implementation-specific + types for which a type like ``std::vector::iterator`` is itself a + typedef will not be transformed. Consider the following examples: + +.. code-block:: c++ + + // The following direct uses of iterator types will be transformed. + std::vector::iterator I = MyVec.begin(); + { + using namespace std; + list::iterator I = MyList.begin(); + } + + // The type specifier for J would transform to auto since it's a typedef + // to a standard iterator type. + typedef std::map::const_iterator map_iterator; + map_iterator J = MyMap.begin(); + + // The following implementation-specific iterator type for which + // std::vector::iterator could be a typedef would not be transformed. + __gnu_cxx::__normal_iterator K = MyVec.begin(); + +* The initializer for the variable being declared is not a braced initializer + list. Otherwise, use of ``auto`` would cause the type of the variable to be + deduced as ``std::initializer_list``. + +New expressions +--------------- + +Frequently, when a pointer is declared and initialized with ``new``, the +pointee type is written twice: in the declaration type and in the +``new`` expression. In this cases, the declaration type can be replaced with +``auto`` improving readability and maintainability. + +.. code-block:: c++ + + TypeName *my_pointer = new TypeName(my_param); + + // becomes + + auto *my_pointer = new TypeName(my_param); + +The check will also replace the declaration type in multiple declarations, if +the following conditions are satisfied: + +* All declared variables have the same type (i.e. all of them are pointers to + the same type). +* All declared variables are initialized with a ``new`` expression. +* The types of all the new expressions are the same than the pointee of the + declaration type. + +.. code-block:: c++ + + TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // becomes + + auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + +Cast expressions +---------------- + +Frequently, when a variable is declared and initialized with a cast, the +variable type is written twice: in the declaration type and in the +cast expression. In this cases, the declaration type can be replaced with +``auto`` improving readability and maintainability. + +.. code-block:: c++ + + TypeName *my_pointer = static_cast(my_param); + + // becomes + + auto *my_pointer = static_cast(my_param); + +The check handles ``static_cast``, ``dynamic_cast``, ``const_cast``, +``reinterpret_cast``, functional casts, C-style casts and function templates +that behave as casts, such as ``llvm::dyn_cast``, ``boost::lexical_cast`` and +``gsl::narrow_cast``. Calls to function templates are considered to behave as +casts if the first template argument is explicit and is a type, and the function +returns that type, or a pointer or reference to it. + +Known Limitations +----------------- + +* If the initializer is an explicit conversion constructor, the check will not + replace the type specifier even though it would be safe to do so. + +* User-defined iterators are not handled at this time. + +Options +------- + +.. option:: RemoveStars + + If the option is set to non-zero (default is `0`), the check will remove + stars from the non-typedef pointer types when replacing type names with + ``auto``. Otherwise, the check will leave stars. For example: + +.. code-block:: c++ + + TypeName *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // RemoveStars = 0 + + auto *my_first_pointer = new TypeName, *my_second_pointer = new TypeName; + + // RemoveStars = 1 + + auto my_first_pointer = new TypeName, my_second_pointer = new TypeName; diff --git a/docs/clang-tidy/checks/modernize-use-bool-literals.rst b/docs/clang-tidy/checks/modernize-use-bool-literals.rst new file mode 100644 index 000000000..7ce048f6a --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-bool-literals.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - modernize-use-bool-literals + +modernize-use-bool-literals +=========================== + +Finds integer literals which are cast to ``bool``. + +.. code-block:: c++ + + bool p = 1; + bool f = static_cast(1); + std::ios_base::sync_with_stdio(0); + bool x = p ? 1 : 0; + + // transforms to + + bool p = true; + bool f = true; + std::ios_base::sync_with_stdio(false); + bool x = p ? true : false; diff --git a/docs/clang-tidy/checks/modernize-use-default-member-init.rst b/docs/clang-tidy/checks/modernize-use-default-member-init.rst new file mode 100644 index 000000000..654cd75c6 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-default-member-init.rst @@ -0,0 +1,49 @@ +.. 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; + }; 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..cc1eb60d1 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-emplace.rst @@ -0,0 +1,101 @@ +.. 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); + // This will be fixed to w.push_back(21, 37); in next version + w.emplace_back(std::make_pair(21L, 37L); + +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 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 fires if any argument of constructor call would be: + + - bitfield (bitfields can't bind to rvalue/universal reference) + + - ``new`` expression (to avoid leak) or if the argument would be converted via + derived-to-base cast. + +This check requires C++11 of higher to run. + +Options +------- + +.. option:: ContainersWithPushBack + + Semicolon-separated list of class names of custom containers that support + ``push_back``. + +.. option:: SmartPointers + + Semicolon-separated list of class names of custom smart pointers. diff --git a/docs/clang-tidy/checks/modernize-use-equals-default.rst b/docs/clang-tidy/checks/modernize-use-equals-default.rst new file mode 100644 index 000000000..b87f883fe --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-equals-default.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - modernize-use-equals-default + +modernize-use-equals-default +============================ + +This check replaces default bodies of special member functions with ``= +default;``. The explicitly defaulted function declarations enable more +opportunities in optimization, because the compiler might treat explicitly +defaulted functions as trivial. + +.. code-block:: c++ + + struct A { + A() {} + ~A(); + }; + A::~A() {} + + // becomes + + struct A { + A() = default; + ~A(); + }; + A::~A() = default; + +.. note:: + Move-constructor and move-assignment operator are not supported yet. diff --git a/docs/clang-tidy/checks/modernize-use-equals-delete.rst b/docs/clang-tidy/checks/modernize-use-equals-delete.rst new file mode 100644 index 000000000..bfa54e4e8 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-equals-delete.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - modernize-use-equals-delete + +modernize-use-equals-delete +=========================== + +This check marks unimplemented private special member functions with ``= delete``. +To avoid false-positives, this check only applies in a translation unit that has +all other member functions implemented. + +.. code-block:: c++ + + struct A { + private: + A(const A&); + A& operator=(const A&); + }; + + // becomes + + struct A { + private: + A(const A&) = delete; + A& operator=(const A&) = delete; + }; + diff --git a/docs/clang-tidy/checks/modernize-use-nullptr.rst b/docs/clang-tidy/checks/modernize-use-nullptr.rst new file mode 100644 index 000000000..fb2d3d99c --- /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:: UserNullMacros + + 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:`UserNullMacros` option is set to ``MY_NULL``. diff --git a/docs/clang-tidy/checks/modernize-use-override.rst b/docs/clang-tidy/checks/modernize-use-override.rst new file mode 100644 index 000000000..f2c778aaa --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-override.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - modernize-use-override + +modernize-use-override +====================== + + +Use C++11's ``override`` and remove ``virtual`` where applicable. diff --git a/docs/clang-tidy/checks/modernize-use-transparent-functors.rst b/docs/clang-tidy/checks/modernize-use-transparent-functors.rst new file mode 100644 index 000000000..88872751e --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-transparent-functors.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - modernize-use-transparent-functors + +modernize-use-transparent-functors +================================== + +Prefer transparent functors to non-transparent ones. When using transparent +functors, the type does not need to be repeated. The code is easier to read, +maintain and less prone to errors. It is not possible to introduce unwanted +conversions. + + .. code-block:: c++ + + // Non-transparent functor + std::map> s; + + // Transparent functor. + std::map> s; + + // Non-transparent functor + using MyFunctor = std::less; + +It is not always a safe transformation though. The following case will be +untouched to preserve the semantics. + + .. code-block:: c++ + + // Non-transparent functor + std::map> s; + +Options +------- + +.. option:: SafeMode + + If the option is set to non-zero, the check will not diagnose cases where + using a transparent functor cannot be guaranteed to produce identical results + as the original code. The default value for this option is `0`. + +This check requires using C++14 or higher to run. diff --git a/docs/clang-tidy/checks/modernize-use-using.rst b/docs/clang-tidy/checks/modernize-use-using.rst new file mode 100644 index 000000000..14172bbf3 --- /dev/null +++ b/docs/clang-tidy/checks/modernize-use-using.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - modernize-use-using + +modernize-use-using +=================== + +The check converts the usage of ``typedef`` with ``using`` keyword. + +Before: + +.. code-block:: c++ + + typedef int variable; + + class Class{}; + typedef void (Class::* MyPtrType)() const; + +After: + +.. code-block:: c++ + + using variable = int; + + class Class{}; + using MyPtrType = void (Class::*)() const; + +This check requires using C++11 or higher to run. diff --git a/docs/clang-tidy/checks/mpi-buffer-deref.rst b/docs/clang-tidy/checks/mpi-buffer-deref.rst new file mode 100644 index 000000000..ef9f391f3 --- /dev/null +++ b/docs/clang-tidy/checks/mpi-buffer-deref.rst @@ -0,0 +1,26 @@ +.. title:: clang-tidy - mpi-buffer-deref + +mpi-buffer-deref +================ + +This check verifies if a buffer passed to an MPI (Message Passing Interface) +function is sufficiently dereferenced. Buffers should be passed as a single +pointer or array. As MPI function signatures specify ``void *`` for their buffer +types, insufficiently dereferenced buffers can be passed, like for example as +double pointers or multidimensional arrays, without a compiler warning emitted. + +Examples: + +.. code-block:: c++ + + // A double pointer is passed to the MPI function. + char *buf; + MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); + + // A multidimensional array is passed to the MPI function. + short buf[1][1]; + MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD); + + // A pointer to an array is passed to the MPI function. + short *buf[1]; + MPI_Send(buf, 1, MPI_SHORT, 0, 0, MPI_COMM_WORLD); diff --git a/docs/clang-tidy/checks/mpi-type-mismatch.rst b/docs/clang-tidy/checks/mpi-type-mismatch.rst new file mode 100644 index 000000000..10752ef3b --- /dev/null +++ b/docs/clang-tidy/checks/mpi-type-mismatch.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - mpi-type-mismatch + +mpi-type-mismatch +================= + +This check verifies if buffer type and MPI (Message Passing Interface) datatype +pairs match for used MPI functions. All MPI datatypes defined by the MPI +standard (3.1) are verified by this check. User defined typedefs, custom MPI +datatypes and null pointer constants are skipped, in the course of verification. + +Example: + +.. code-block:: c++ + + // In this case, the buffer type matches MPI datatype. + char buf; + MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); + + // In the following case, the buffer type does not match MPI datatype. + int buf; + MPI_Send(&buf, 1, MPI_CHAR, 0, 0, MPI_COMM_WORLD); diff --git a/docs/clang-tidy/checks/performance-faster-string-find.rst b/docs/clang-tidy/checks/performance-faster-string-find.rst new file mode 100644 index 000000000..8d7265e68 --- /dev/null +++ b/docs/clang-tidy/checks/performance-faster-string-find.rst @@ -0,0 +1,28 @@ +.. title:: clang-tidy - performance-faster-string-find + +performance-faster-string-find +============================== + +Optimize calls to ``std::string::find()`` and friends when the needle passed is +a single character string literal. The character literal overload is more +efficient. + +Examples: + +.. code-block:: c++ + + str.find("A"); + + // becomes + + str.find('A'); + +Options +------- + +.. option:: StringLikeClasses + + Semicolon-separated list of names of string-like classes. By default only + ``std::basic_string`` is considered. The list of methods to consired is + fixed. + diff --git a/docs/clang-tidy/checks/performance-for-range-copy.rst b/docs/clang-tidy/checks/performance-for-range-copy.rst new file mode 100644 index 000000000..2e7db98d8 --- /dev/null +++ b/docs/clang-tidy/checks/performance-for-range-copy.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - performance-for-range-copy + +performance-for-range-copy +========================== + +Finds C++11 for ranges where the loop variable is copied in each iteration but +it would suffice to obtain it by const reference. + +The check is only applied to loop variables of types that are expensive to copy +which means they are not trivially copyable or have a non-trivial copy +constructor or destructor. + +To ensure that it is safe to replace the copy with a const reference the +following heuristic is employed: + +1. The loop variable is const qualified. +2. The loop variable is not const, but only const methods or operators are + invoked on it, or it is used as const reference or value argument in + constructors or function calls. + +Options +------- + +.. option:: WarnOnAllAutoCopies + + When non-zero, warns on any use of `auto` as the type of the range-based for + loop variable. Default is `0`. diff --git a/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst b/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst new file mode 100644 index 000000000..7a5cdf419 --- /dev/null +++ b/docs/clang-tidy/checks/performance-implicit-cast-in-loop.rst @@ -0,0 +1,21 @@ +.. title:: clang-tidy - performance-implicit-cast-in-loop + +performance-implicit-cast-in-loop +================================= + +This warning appears in a range-based loop with a loop variable of const ref +type where the type of the variable does not match the one returned by the +iterator. This means that an implicit cast has been added, which can for example +result in expensive deep copies. + +Example: + +.. code-block:: c++ + + map> my_map; + for (const pair>& p : my_map) {} + // The iterator type is in fact pair>, which means + // that the compiler added a cast, resulting in a copy of the vectors. + +The easiest solution is usually to use ``const auto&`` instead of writing the type +manually. diff --git a/docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst b/docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst new file mode 100644 index 000000000..60910c6f7 --- /dev/null +++ b/docs/clang-tidy/checks/performance-inefficient-string-concatenation.rst @@ -0,0 +1,59 @@ +.. title:: clang-tidy - performance-inefficient-string-concatenation + +performance-inefficient-string-concatenation +============================================ + +This check warns about the performance overhead arising from concatenating +strings using the ``operator+``, for instance: + +.. code-block:: c++ + + std::string a("Foo"), b("Bar"); + a = a + b; + +Instead of this structure you should use ``operator+=`` or ``std::string``'s +(``std::basic_string``) class member function ``append()``. For instance: + +.. code-block:: c++ + + std::string a("Foo"), b("Baz"); + for (int i = 0; i < 20000; ++i) { + a = a + "Bar" + b; + } + +Could be rewritten in a greatly more efficient way like: + +.. code-block:: c++ + + std::string a("Foo"), b("Baz"); + for (int i = 0; i < 20000; ++i) { + a.append("Bar").append(b); + } + +And this can be rewritten too: + +.. code-block:: c++ + + void f(const std::string&) {} + std::string a("Foo"), b("Baz"); + void g() { + f(a + "Bar" + b); + } + +In a slightly more efficient way like: + +.. code-block:: c++ + + void f(const std::string&) {} + std::string a("Foo"), b("Baz"); + void g() { + f(std::string(a).append("Bar").append(b)); + } + +Options +------- + +.. option:: StrictMode + + When zero, the check will only check the string usage in ``while``, ``for`` + and ``for-range`` statements. Default is `0`. diff --git a/docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst b/docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst new file mode 100644 index 000000000..fcd037514 --- /dev/null +++ b/docs/clang-tidy/checks/performance-type-promotion-in-math-fn.rst @@ -0,0 +1,11 @@ +.. 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). diff --git a/docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst b/docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst new file mode 100644 index 000000000..2bd77551a --- /dev/null +++ b/docs/clang-tidy/checks/performance-unnecessary-copy-initialization.rst @@ -0,0 +1,37 @@ +.. title:: clang-tidy - performance-unnecessary-copy-initialization + +performance-unnecessary-copy-initialization +=========================================== + +Finds local variable declarations that are initialized using the copy +constructor of a non-trivially-copyable type but it would suffice to obtain a +const reference. + +The check is only applied if it is safe to replace the copy by a const +reference. This is the case when the variable is const qualified or when it is +only used as a const, i.e. only const methods or operators are invoked on it, or +it is used as const reference or value argument in constructors or function +calls. + +Example: + +.. code-block:: c++ + + const string& constReference(); + void Function() { + // The warning will suggest making this a const reference. + const string UnnecessaryCopy = constReference(); + } + + struct Foo { + const string& name() const; + }; + void Function(const Foo& foo) { + // The warning will suggest making this a const reference. + string UnnecessaryCopy1 = foo.name(); + UnnecessaryCopy1.find("bar"); + + // The warning will suggest making this a const reference. + string UnnecessaryCopy2 = UnnecessaryCopy1; + UnnecessaryCopy2.find("bar"); + } diff --git a/docs/clang-tidy/checks/performance-unnecessary-value-param.rst b/docs/clang-tidy/checks/performance-unnecessary-value-param.rst new file mode 100644 index 000000000..e69610ecf --- /dev/null +++ b/docs/clang-tidy/checks/performance-unnecessary-value-param.rst @@ -0,0 +1,63 @@ +.. title:: clang-tidy - performance-unnecessary-value-param + +performance-unnecessary-value-param +=================================== + +Flags value parameter declarations of expensive to copy types that are copied +for each invocation but it would suffice to pass them by const reference. + +The check is only applied to parameters of types that are expensive to copy +which means they are not trivially copyable or have a non-trivial copy +constructor or destructor. + +To ensure that it is safe to replace the value parameter with a const reference +the following heuristic is employed: + +1. the parameter is const qualified; +2. the parameter is not const, but only const methods or operators are invoked + on it, or it is used as const reference or value argument in constructors or + function calls. + +Example: + +.. code-block:: c++ + + void f(const string Value) { + // The warning will suggest making Value a reference. + } + + void g(ExpensiveToCopy Value) { + // The warning will suggest making Value a const reference. + Value.ConstMethd(); + ExpensiveToCopy Copy(Value); + } + +If the parameter is not const, only copied or assigned once and has a +non-trivial move-constructor or move-assignment operator respectively the check +will suggest to move it. + +Example: + +.. code-block:: c++ + + void setValue(string Value) { + Field = Value; + } + +Will become: + +.. code-block:: c++ + + #include + + void setValue(string Value) { + Field = std::move(Value); + } + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. diff --git a/docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst b/docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst new file mode 100644 index 000000000..3aea5d0c0 --- /dev/null +++ b/docs/clang-tidy/checks/readability-avoid-const-params-in-decls.rst @@ -0,0 +1,17 @@ +.. title:: clang-tidy - readability-avoid-const-params-in-decls + +readability-avoid-const-params-in-decls +======================================= + +Checks whether a function declaration has parameters that are top level +``const``. + +``const`` values in declarations do not affect the signature of a function, so +they should not be put there. + +Examples: + +.. code-block:: c++ + + void f(const string); // Bad: const is top level. + void f(const string&); // Good: const is not top level. diff --git a/docs/clang-tidy/checks/readability-braces-around-statements.rst b/docs/clang-tidy/checks/readability-braces-around-statements.rst new file mode 100644 index 000000000..2c0816591 --- /dev/null +++ b/docs/clang-tidy/checks/readability-braces-around-statements.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - readability-braces-around-statements + +readability-braces-around-statements +==================================== + +`google-readability-braces-around-statements` redirects here as an alias for +this check. + +Checks that bodies of ``if`` statements and loops (``for``, ``do while``, and +``while``) are inside braces. + +Before: + +.. code-block:: c++ + + if (condition) + statement; + +After: + +.. code-block:: c++ + + if (condition) { + statement; + } + +Options +------- + +.. option:: ShortStatementLines + + Defines the minimal number of lines that the statement should have in order + to trigger this check. + + The number of lines is counted from the end of condition or initial keyword + (``do``/``else``) until the last line of the inner statement. Default value + `0` means that braces will be added to all statements (not having them + already). diff --git a/docs/clang-tidy/checks/readability-container-size-empty.rst b/docs/clang-tidy/checks/readability-container-size-empty.rst new file mode 100644 index 000000000..c971ca7b0 --- /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..fbefa9719 --- /dev/null +++ b/docs/clang-tidy/checks/readability-function-size.rst @@ -0,0 +1,27 @@ +.. 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). diff --git a/docs/clang-tidy/checks/readability-identifier-naming.rst b/docs/clang-tidy/checks/readability-identifier-naming.rst new file mode 100644 index 000000000..30408c12b --- /dev/null +++ b/docs/clang-tidy/checks/readability-identifier-naming.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - readability-identifier-naming + +readability-identifier-naming +============================= + +Checks for identifiers naming style mismatch. + +This check will try to enforce coding guidelines on the identifiers naming. +It supports `lower_case`, `UPPER_CASE`, `camelBack` and `CamelCase` casing and +tries to convert from one to another if a mismatch is detected. + +It also supports a fixed prefix and suffix that will be prepended or +appended to the identifiers, regardless of the casing. + +Many configuration options are available, in order to be able to create +different rules for different kind of identifier. In general, the +rules are falling back to a more generic rule if the specific case is not +configured. diff --git a/docs/clang-tidy/checks/readability-implicit-bool-cast.rst b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst new file mode 100644 index 000000000..bcd23abf7 --- /dev/null +++ b/docs/clang-tidy/checks/readability-implicit-bool-cast.rst @@ -0,0 +1,130 @@ +.. title:: clang-tidy - readability-implicit-bool-cast + +readability-implicit-bool-cast +============================== + +This check can be used to find implicit conversions between built-in types and +booleans. Depending on use case, it may simply help with readability of the code, +or in some cases, point to potential bugs which remain unnoticed due to implicit +conversions. + +The following is a real-world example of bug which was hiding behind implicit +``bool`` cast: + +.. code-block:: c++ + + class Foo { + int m_foo; + + public: + void setFoo(bool foo) { m_foo = foo; } // warning: implicit cast bool -> int + int getFoo() { return m_foo; } + }; + + void use(Foo& foo) { + bool value = foo.getFoo(); // warning: implicit cast int -> bool + } + +This code is the result of unsuccessful refactoring, where type of ``m_foo`` +changed from ``bool`` to ``int``. The programmer forgot to change all +occurrences of ``bool``, and the remaining code is no longer correct, yet it +still compiles without any visible warnings. + +In addition to issuing warnings, fix-it hints are provided to help solve the +reported issues. This can be used for improving readability of code, for +example: + +.. code-block:: c++ + + void conversionsToBool() { + float floating; + bool boolean = floating; + // ^ propose replacement: bool boolean = floating != 0.0f; + + int integer; + if (integer) {} + // ^ propose replacement: if (integer != 0) {} + + int* pointer; + if (!pointer) {} + // ^ propose replacement: if (pointer == nullptr) {} + + while (1) {} + // ^ propose replacement: while (true) {} + } + + void functionTakingInt(int param); + + void conversionsFromBool() { + bool boolean; + functionTakingInt(boolean); + // ^ propose replacement: functionTakingInt(static_cast(boolean)); + + functionTakingInt(true); + // ^ propose replacement: functionTakingInt(1); + } + +In general, the following cast types are checked: + +- integer expression/literal to boolean, + +- floating expression/literal to boolean, + +- pointer/pointer to member/``nullptr``/``NULL`` to boolean, + +- boolean expression/literal to integer, + +- boolean expression/literal to floating. + +The rules for generating fix-it hints are: + +- in case of casts from other built-in type to bool, an explicit comparison + is proposed to make it clear what exaclty is being compared: + + - ``bool boolean = floating;`` is changed to + ``bool boolean = floating == 0.0f;``, + + - for other types, appropriate literals are used (``0``, ``0u``, ``0.0f``, + ``0.0``, ``nullptr``), + +- in case of negated expressions cast to bool, the proposed replacement with + comparison is simplified: + + - ``if (!pointer)`` is changed to ``if (pointer == nullptr)``, + +- in case of casts from bool to other built-in types, an explicit ``static_cast`` + is proposed to make it clear that a cast is taking place: + + - ``int integer = boolean;`` is changed to + ``int integer = static_cast(boolean);``, + +- if the cast is performed on type literals, an equivalent literal is proposed, + according to what type is actually expected, for example: + + - ``functionTakingBool(0);`` is changed to ``functionTakingBool(false);``, + + - ``functionTakingInt(true);`` is changed to ``functionTakingInt(1);``, + + - for other types, appropriate literals are used (``false``, ``true``, ``0``, + ``1``, ``0u``, ``1u``, ``0.0f``, ``1.0f``, ``0.0``, ``1.0f``). + +Some additional accommodations are made for pre-C++11 dialects: + +- ``false`` literal cast to pointer is detected, + +- instead of ``nullptr`` literal, ``0`` is proposed as replacement. + +Occurrences of implicit casts inside macros and template instantiations are +deliberately ignored, as it is not clear how to deal with such cases. + +Options +------- + +.. option:: AllowConditionalIntegerCasts + + When non-zero, the check will allow conditional integer casts. Default is + `0`. + +.. option:: AllowConditionalPointerCasts + + When non-zero, the check will allow conditional pointer casts. Default is `0`. diff --git a/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst new file mode 100644 index 000000000..599f2815d --- /dev/null +++ b/docs/clang-tidy/checks/readability-inconsistent-declaration-parameter-name.rst @@ -0,0 +1,44 @@ +.. title:: clang-tidy - readability-inconsistent-declaration-parameter-name + +readability-inconsistent-declaration-parameter-name +=================================================== + +Find function declarations which differ in parameter names. + +Example: + +.. code-block:: c++ + + // in foo.hpp: + void foo(int a, int b, int c); + + // in foo.cpp: + void foo(int d, int e, int f); // warning + +This check should help to enforce consistency in large projects, where it often +happens that a definition of function is refactored, changing the parameter +names, but its declaration in header file is not updated. With this check, we +can easily find and correct such inconsistencies, keeping declaration and +definition always in sync. + +Unnamed parameters are allowed and are not taken into account when comparing +function declarations, for example: + +.. code-block:: c++ + + void foo(int a); + void foo(int); // no warning + +To help with refactoring, in some cases fix-it hints are generated to align +parameter names to a single naming convention. This works with the assumption +that the function definition is the most up-to-date version, as it directly +references parameter names in its body. Example: + +.. code-block:: c++ + + void foo(int a); // warning and fix-it hint (replace "a" to "b") + int foo(int b) { return b + 2; } // definition with use of "b" + +In the case of multiple redeclarations or function template specializations, +a warning is issued for every redeclaration or specialization inconsistent with +the definition or the first declaration seen in a translation unit. diff --git a/docs/clang-tidy/checks/readability-misplaced-array-index.rst b/docs/clang-tidy/checks/readability-misplaced-array-index.rst new file mode 100644 index 000000000..26a5af779 --- /dev/null +++ b/docs/clang-tidy/checks/readability-misplaced-array-index.rst @@ -0,0 +1,27 @@ +.. title:: clang-tidy - readability-misplaced-array-index + +readability-misplaced-array-index +================================= + +This check warns for unusual array index syntax. + +The following code has unusual array index syntax: + +.. code-block:: c++ + + void f(int *X, int Y) { + Y[X] = 0; + } + +becomes + +.. code-block:: c++ + + void f(int *X, int Y) { + X[Y] = 0; + } + +The check warns about such unusual syntax for readability reasons: + * There are programmers that are not familiar with this unusual syntax. + * It is possible that variables are mixed up. + diff --git a/docs/clang-tidy/checks/readability-named-parameter.rst b/docs/clang-tidy/checks/readability-named-parameter.rst new file mode 100644 index 000000000..8d28c0aa0 --- /dev/null +++ b/docs/clang-tidy/checks/readability-named-parameter.rst @@ -0,0 +1,16 @@ +.. title:: clang-tidy - readability-named-parameter + +readability-named-parameter +=========================== + +Find functions with unnamed arguments. + +The check implements the following rule originating in the Google C++ Style +Guide: + +https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions + +All parameters should be named, with identical names in the declaration and +implementation. + +Corresponding cpplint.py check name: `readability/function`. diff --git a/docs/clang-tidy/checks/readability-non-const-parameter.rst b/docs/clang-tidy/checks/readability-non-const-parameter.rst new file mode 100644 index 000000000..0729470fa --- /dev/null +++ b/docs/clang-tidy/checks/readability-non-const-parameter.rst @@ -0,0 +1,46 @@ +.. title:: clang-tidy - readability-non-const-parameter + +readability-non-const-parameter +=============================== + +The check finds function parameters of a pointer type that could be changed to +point to a constant type instead. + +When ``const`` is used properly, many mistakes can be avoided. Advantages when +using ``const`` properly: + +- prevent unintentional modification of data; + +- get additional warnings such as using uninitialized data; + +- make it easier for developers to see possible side effects. + +This check is not strict about constness, it only warns when the constness will +make the function interface safer. + +.. code-block:: c++ + + // warning here; the declaration "const char *p" would make the function + // interface safer. + char f1(char *p) { + return *p; + } + + // no warning; the declaration could be more const "const int * const p" but + // that does not make the function interface safer. + int f2(const int *p) { + return *p; + } + + // no warning; making x const does not make the function interface safer + int f3(int x) { + return x; + } + + // no warning; Technically, *p can be const ("const struct S *p"). But making + // *p const could be misleading. People might think that it's safe to pass + // const data to this function. + struct S { int *a; int *b; }; + int f3(struct S *p) { + *(p->a) = 0; + } diff --git a/docs/clang-tidy/checks/readability-redundant-control-flow.rst b/docs/clang-tidy/checks/readability-redundant-control-flow.rst new file mode 100644 index 000000000..aeaf345b9 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-control-flow.rst @@ -0,0 +1,50 @@ +.. title:: clang-tidy - readability-redundant-control-flow + +readability-redundant-control-flow +================================== + +This check looks for procedures (functions returning no value) with ``return`` +statements at the end of the function. Such ``return`` statements are redundant. + +Loop statements (``for``, ``while``, ``do while``) are checked for redundant +``continue`` statements at the end of the loop body. + +Examples: + +The following function `f` contains a redundant ``return`` statement: + +.. code-block:: c++ + + extern void g(); + void f() { + g(); + return; + } + +becomes + +.. code-block:: c++ + + extern void g(); + void f() { + g(); + } + +The following function `k` contains a redundant ``continue`` statement: + +.. code-block:: c++ + + void k() { + for (int i = 0; i < 10; ++i) { + continue; + } + } + +becomes + +.. code-block:: c++ + + void k() { + for (int i = 0; i < 10; ++i) { + } + } diff --git a/docs/clang-tidy/checks/readability-redundant-declaration.rst b/docs/clang-tidy/checks/readability-redundant-declaration.rst new file mode 100644 index 000000000..4b5d0a5d6 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-declaration.rst @@ -0,0 +1,29 @@ +.. title:: clang-tidy - readability-redundant-declaration + +readability-redundant-declaration +================================= + +Finds redundant variable and function declarations. + +.. code-block:: c++ + + extern int X; + extern int X; + +becomes + +.. code-block:: c++ + + extern int X; + +Such redundant declarations can be removed without changing program behaviour. +They can for instance be unintentional left overs from previous refactorings +when code has been moved around. Having redundant declarations could in worst +case mean that there are typos in the code that cause bugs. + +Normally the code can be automatically fixed, :program:`clang-tidy` can remove +the second declaration. However there are 2 cases when you need to fix the code +manually: + +* When the declarations are in different header files; +* When multiple variables are declared together. diff --git a/docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst b/docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst new file mode 100644 index 000000000..d8c7ec788 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-function-ptr-dereference.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - readability-redundant-function-ptr-dereference + +readability-redundant-function-ptr-dereference +============================================== + +Finds redundant dereferences of a function pointer. + +Before: + +.. code-block:: c++ + + int f(int,int); + int (*p)(int, int) = &f; + + int i = (**p)(10, 50); + +After: + +.. code-block:: c++ + + int f(int,int); + int (*p)(int, int) = &f; + + int i = (*p)(10, 50); diff --git a/docs/clang-tidy/checks/readability-redundant-member-init.rst b/docs/clang-tidy/checks/readability-redundant-member-init.rst new file mode 100644 index 000000000..a640e16e3 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-member-init.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - readability-redundant-member-init + +readability-redundant-member-init +================================= + +Finds member initializations that are unnecessary because the same default +constructor would be called if they were not present. + +Example: + +.. code-block:: c++ + + // Explicitly initializing the member s is unnecessary. + class Foo { + public: + Foo() : s() {} + + private: + std::string s; + }; diff --git a/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst new file mode 100644 index 000000000..acf79860d --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-smartptr-get.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - readability-redundant-smartptr-get + +readability-redundant-smartptr-get +================================== + +`google-readability-redundant-smartptr-get` redirects here as an alias for this +check. + +Find and remove redundant calls to smart pointer's ``.get()`` method. + +Examples: + +.. code-block:: c++ + + ptr.get()->Foo() ==> ptr->Foo() + *ptr.get() ==> *ptr + *ptr->get() ==> **ptr + diff --git a/docs/clang-tidy/checks/readability-redundant-string-cstr.rst b/docs/clang-tidy/checks/readability-redundant-string-cstr.rst new file mode 100644 index 000000000..e6760a41c --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-string-cstr.rst @@ -0,0 +1,7 @@ +.. title:: clang-tidy - readability-redundant-string-cstr + +readability-redundant-string-cstr +================================= + + +Finds unnecessary calls to ``std::string::c_str()`` and ``std::string::data()``. diff --git a/docs/clang-tidy/checks/readability-redundant-string-init.rst b/docs/clang-tidy/checks/readability-redundant-string-init.rst new file mode 100644 index 000000000..a5530a9a8 --- /dev/null +++ b/docs/clang-tidy/checks/readability-redundant-string-init.rst @@ -0,0 +1,14 @@ +.. 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(""); diff --git a/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst b/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst new file mode 100644 index 000000000..b333e186d --- /dev/null +++ b/docs/clang-tidy/checks/readability-simplify-boolean-expr.rst @@ -0,0 +1,86 @@ +.. title:: clang-tidy - readability-simplify-boolean-expr + +readability-simplify-boolean-expr +================================= + +Looks for boolean expressions involving boolean constants and simplifies +them to use the appropriate boolean expression directly. + +Examples: + +=========================================== ================ +Initial expression Result +------------------------------------------- ---------------- +``if (b == true)`` ``if (b)`` +``if (b == false)`` ``if (!b)`` +``if (b && true)`` ``if (b)`` +``if (b && false)`` ``if (false)`` +``if (b || true)`` ``if (true)`` +``if (b || false)`` ``if (b)`` +``e ? true : false`` ``e`` +``e ? false : true`` ``!e`` +``if (true) t(); else f();`` ``t();`` +``if (false) t(); else f();`` ``f();`` +``if (e) return true; else return false;`` ``return e;`` +``if (e) return false; else return true;`` ``return !e;`` +``if (e) b = true; else b = false;`` ``b = e;`` +``if (e) b = false; else b = true;`` ``b = !e;`` +``if (e) return true; return false;`` ``return e;`` +``if (e) return false; return true;`` ``return !e;`` +=========================================== ================ + +The resulting expression ``e`` is modified as follows: + 1. Unnecessary parentheses around the expression are removed. + 2. Negated applications of ``!`` are eliminated. + 3. Negated applications of comparison operators are changed to use the + opposite condition. + 4. Implicit conversions of pointers, including pointers to members, to + ``bool`` are replaced with explicit comparisons to ``nullptr`` in C++11 + or ``NULL`` in C++98/03. + 5. Implicit casts to ``bool`` are replaced with explicit casts to ``bool``. + 6. Object expressions with ``explicit operator bool`` conversion operators + are replaced with explicit casts to ``bool``. + 7. Implicit conversions of integral types to ``bool`` are replaced with + explicit comparisons to ``0``. + +Examples: + 1. The ternary assignment ``bool b = (i < 0) ? true : false;`` has redundant + parentheses and becomes ``bool b = i < 0;``. + + 2. The conditional return ``if (!b) return false; return true;`` has an + implied double negation and becomes ``return b;``. + + 3. The conditional return ``if (i < 0) return false; return true;`` becomes + ``return i >= 0;``. + + The conditional return ``if (i != 0) return false; return true;`` becomes + ``return i == 0;``. + + 4. The conditional return ``if (p) return true; return false;`` has an + implicit conversion of a pointer to ``bool`` and becomes + ``return p != nullptr;``. + + The ternary assignment ``bool b = (i & 1) ? true : false;`` has an + implicit conversion of ``i & 1`` to ``bool`` and becomes + ``bool b = (i & 1) != 0;``. + + 5. The conditional return ``if (i & 1) return true; else return false;`` has + an implicit conversion of an integer quantity ``i & 1`` to ``bool`` and + becomes ``return (i & 1) != 0;`` + + 6. Given ``struct X { explicit operator bool(); };``, and an instance ``x`` of + ``struct X``, the conditional return ``if (x) return true; return false;`` + becomes ``return static_cast(x);`` + +Options +------- + +.. option:: ChainedConditionalReturn + + If non-zero, conditional boolean return statements at the end of an + ``if/else if`` chain will be transformed. Default is `0`. + +.. option:: ChainedConditionalAssignment + + If non-zero, conditional boolean assignments at the end of an ``if/else + if`` chain will be transformed. Default is `0`. diff --git a/docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst b/docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst new file mode 100644 index 000000000..c1803d4b1 --- /dev/null +++ b/docs/clang-tidy/checks/readability-static-definition-in-anonymous-namespace.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - readability-static-definition-in-anonymous-namespace + +readability-static-definition-in-anonymous-namespace +==================================================== + +Finds static function and variable definitions in anonymous namespace. + +In this case, ``static`` is redundant, because anonymous namespace limits the +visibility of definitions to a single translation unit. + +.. code-block:: c++ + + namespace { + static int a = 1; // Warning. + static const b = 1; // Warning. + } + +The check will apply a fix by removing the redundant ``static`` qualifier. diff --git a/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst b/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst new file mode 100644 index 000000000..83122356d --- /dev/null +++ b/docs/clang-tidy/checks/readability-uniqueptr-delete-release.rst @@ -0,0 +1,7 @@ +.. 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. diff --git a/docs/clang-tidy/index.rst b/docs/clang-tidy/index.rst new file mode 100644 index 000000000..fdf0adbe7 --- /dev/null +++ b/docs/clang-tidy/index.rst @@ -0,0 +1,640 @@ +========== +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 +====================== ========================================================= +``boost-`` Checks related to Boost library. +``cert-`` Checks related to CERT Secure Coding Guidelines. +``cppcoreguidelines-`` Checks related to C++ Core Guidelines. +``clang-analyzer-`` Clang Static Analyzer checks. +``google-`` Checks related to the Google coding conventions. +``llvm-`` Checks related to the LLVM coding conventions. +``misc-`` Checks that we didn't have a better category for. +``modernize-`` Checks that advocate usage of modern (currently "modern" + means "C++11") language constructs. +``mpi-`` Checks related to MPI (Message Passing Interface). +``performance-`` Checks that target performance-related issues. +``readability-`` Checks that target readability-related issues that don't + relate to any particular coding style. +====================== ========================================================= + +Clang diagnostics are treated in a similar way as check diagnostics. Clang +diagnostics are displayed by :program:`clang-tidy` and can be filtered out using +``-checks=`` option. However, the ``-checks=`` option does not affect +compilation arguments, so it can not turn on Clang warnings which are not +already turned on in build configuration. The ``-warnings-as-errors=`` option +upgrades any warnings emitted under the ``-checks=`` flag to errors (but it +does not enable any checks itself). + +Clang diagnostics have check names starting with ``clang-diagnostic-``. +Diagnostics which have a corresponding warning option, are named +``clang-diagnostic-``, e.g. Clang warning controlled by +``-Wliteral-conversion`` will be reported with check name +``clang-diagnostic-literal-conversion``. + +The ``-fix`` flag instructs :program:`clang-tidy` to fix found errors if +supported by corresponding checks. + +An overview of all the command-line options: + +.. code-block:: console + + $ clang-tidy -help + USAGE: clang-tidy [options] [... ] + + OPTIONS: + + Generic Options: + + -help - Display available options (-help-hidden for more) + -help-list - Display list of available options (-help-list-hidden for more) + -version - Display the version of this program + + clang-tidy options: + + -analyze-temporary-dtors - + Enable temporary destructor-aware analysis in + clang-analyzer- checks. + This option overrides the value read from a + .clang-tidy file. + -checks= - + Comma-separated list of globs with optional '-' + prefix. Globs are processed in order of + appearance in the list. Globs without '-' + prefix add checks with matching names to the + set, globs with the '-' prefix remove checks + with matching names from the set of enabled + checks. This option's value is appended to the + value of the 'Checks' option in .clang-tidy + file, if any. + -config= - + Specifies a configuration in YAML/JSON format: + -config="{Checks: '*', + CheckOptions: [{key: x, + value: y}]}" + When the value is empty, clang-tidy will + attempt to find a file named .clang-tidy for + each source file in its parent directories. + -dump-config - + Dumps configuration in the YAML format to + stdout. This option can be used along with a + file name (and '--' if the file is outside of a + project with configured compilation database). + The configuration used for this file will be + printed. + Use along with -checks=* to include + configuration of all checks. + -enable-check-profile - + Enable per-check timing profiles, and print a + report to stderr. + -explain-config - + For each enabled check explains, where it is + enabled, i.e. in clang-tidy binary, command + line or a specific configuration file. + -export-fixes= - + YAML file to store suggested fixes in. The + stored fixes can be applied to the input source + code with clang-apply-replacements. + -extra-arg= - Additional argument to append to the compiler command line + -extra-arg-before= - Additional argument to prepend to the compiler command line + -fix - + Apply suggested fixes. Without -fix-errors + clang-tidy will bail out if any compilation + errors were found. + -fix-errors - + Apply suggested fixes even if compilation + errors were found. If compiler errors have + attached fix-its, clang-tidy will apply them as + well. + -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 + -style= - + Fallback style for reformatting after inserting fixes + if there is no clang-format config file found. + -system-headers - Display the errors from system headers. + -warnings-as-errors= - + Upgrades warnings to errors. Same format as + '-checks'. + This option's value is appended to the value of + the 'WarningsAsErrors' option in .clang-tidy + file, if any. + + -p is used to read a compile command database. + + For example, it can be a CMake build directory in which a file named + compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + CMake option to get this output). When no build path is specified, + a search for compile_commands.json will be attempted through all + parent paths of the first input file . See: + http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an + example of setting up Clang Tooling on a source tree. + + ... specify the paths of source files. These paths are + looked up in the compile command database. If the path of a file is + absolute, it needs to point into CMake's source tree. If the path is + relative, the current working directory needs to be in the CMake + source tree and the file must be in a subdirectory of the current + working directory. "./" prefixes in the relative files will be + automatically removed, but the rest of a relative path must be a + suffix of a path in the compile command database. + + + Configuration files: + clang-tidy attempts to read configuration for each source file from a + .clang-tidy file located in the closest parent directory of the source + file. If any configuration options have a corresponding command-line + option, command-line option takes precedence. The effective + configuration can be inspected using -dump-config: + + $ clang-tidy -dump-config - -- + --- + Checks: '-*,some-check' + WarningsAsErrors: '' + HeaderFilterRegex: '' + AnalyzeTemporaryDtors: false + User: user + CheckOptions: + - key: some-check.SomeOption + value: 'some value' + ... + +.. _LibTooling: http://clang.llvm.org/docs/LibTooling.html +.. _How To Setup Tooling For LLVM: http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html + + +Getting Involved +================ + +:program:`clang-tidy` has several own checks and can run Clang static analyzer +checks, but its power is in the ability to easily write custom checks. + +Checks are organized in modules, which can be linked into :program:`clang-tidy` +with minimal or no code changes in :program:`clang-tidy`. + +Checks can plug into the analysis on the preprocessor level using `PPCallbacks`_ +or on the AST level using `AST Matchers`_. When an error is found, checks can +report them in a way similar to how Clang diagnostics work. A fix-it hint can be +attached to a diagnostic message. + +The interface provided by :program:`clang-tidy` makes it easy to write useful +and precise checks in just a few lines of code. If you have an idea for a good +check, the rest of this document explains how to do this. + +There are a few tools particularly useful when developing clang-tidy checks: + * ``add_new_check.py`` is a script to automate the process of adding a new + check, it will create the check, update the CMake file and create a test; + * ``rename_check.py`` does what the script name suggests, renames an existing + check; + * :program:`clang-query` is invaluable for interactive prototyping of AST + matchers and exploration of the Clang AST; + * `clang-check`_ with the ``-ast-dump`` (and optionally ``-ast-dump-filter``) + provides a convenient way to dump AST of a C++ program. + + +.. _AST Matchers: http://clang.llvm.org/docs/LibASTMatchers.html +.. _PPCallbacks: http://clang.llvm.org/doxygen/classclang_1_1PPCallbacks.html +.. _clang-check: http://clang.llvm.org/docs/ClangCheck.html + + +Choosing the Right Place for your Check +--------------------------------------- + +If you have an idea of a check, you should decide whether it should be +implemented as a: + ++ *Clang diagnostic*: if the check is generic enough, targets code patterns that + most probably are bugs (rather than style or readability issues), can be + implemented effectively and with extremely low false positive rate, it may + make a good Clang diagnostic. + ++ *Clang static analyzer check*: if the check requires some sort of control flow + analysis, it should probably be implemented as a static analyzer check. + ++ *clang-tidy check* is a good choice for linter-style checks, checks that are + related to a certain coding style, checks that address code readability, etc. + + +Preparing your Workspace +------------------------ + +If you are new to LLVM development, you should read the `Getting Started with +the LLVM System`_, `Using Clang Tools`_ and `How To Setup Tooling For LLVM`_ +documents to check out and build LLVM, Clang and Clang Extra Tools with CMake. + +Once you are done, change to the ``llvm/tools/clang/tools/extra`` directory, and +let's start! + +.. _Getting Started with the LLVM System: http://llvm.org/docs/GettingStarted.html +.. _Using Clang Tools: http://clang.llvm.org/docs/ClangTools.html + + +The Directory Structure +----------------------- + +:program:`clang-tidy` source code resides in the +``llvm/tools/clang/tools/extra`` directory and is structured as follows: + +:: + + clang-tidy/ # Clang-tidy core. + |-- ClangTidy.h # Interfaces for users and checks. + |-- ClangTidyModule.h # Interface for clang-tidy modules. + |-- ClangTidyModuleRegistry.h # Interface for registering of modules. + ... + |-- google/ # Google clang-tidy module. + |-+ + |-- GoogleTidyModule.cpp + |-- GoogleTidyModule.h + ... + |-- llvm/ # LLVM clang-tidy module. + |-+ + |-- LLVMTidyModule.cpp + |-- LLVMTidyModule.h + ... + |-- tool/ # Sources of the clang-tidy binary. + ... + test/clang-tidy/ # Integration tests. + ... + unittests/clang-tidy/ # Unit tests. + |-- ClangTidyTest.h + |-- GoogleModuleTest.cpp + |-- LLVMModuleTest.cpp + ... + + +Writing a clang-tidy Check +-------------------------- + +So you have an idea of a useful check for :program:`clang-tidy`. + +First, if you're not familiar with LLVM development, read through the `Getting +Started with LLVM`_ document for instructions on setting up your workflow and +the `LLVM Coding Standards`_ document to familiarize yourself with the coding +style used in the project. For code reviews we mostly use `LLVM Phabricator`_. + +.. _Getting Started with LLVM: http://llvm.org/docs/GettingStarted.html +.. _LLVM Coding Standards: http://llvm.org/docs/CodingStandards.html +.. _LLVM Phabricator: http://llvm.org/docs/Phabricator.html + +Next, you need to decide which module the check belongs to. Modules +are located in subdirectories of `clang-tidy/ +`_ +and contain checks targeting a certain aspect of code quality (performance, +readability, etc.), certain coding style or standard (Google, LLVM, CERT, etc.) +or a widely used API (e.g. MPI). Their names are same as user-facing check +groups names described :ref:`above `. + +After choosing the module and the name for the check, run the +``clang-tidy/add_new_check.py`` script to create the skeleton of the check and +plug it to :program:`clang-tidy`. It's the recommended way of adding new checks. + +If we want to create a `readability-awesome-function-names`, we would run: + +.. code-block:: console + + $ clang-tidy/add_new_check.py readability awesome-function-names + + +The ``add_new_check.py`` script will: + * create the class for your check inside the specified module's directory and + register it in the module and in the build system; + * create a lit test file in the ``test/clang-tidy/`` directory; + * create a documentation file and include it into the + ``docs/clang-tidy/checks/list.rst``. + +Let's see in more detail at the check class definition: + +.. code-block:: c++ + + ... + + #include "../ClangTidy.h" + + namespace clang { + namespace tidy { + namespace readability { + + ... + class AwesomeFunctionNamesCheck : public ClangTidyCheck { + public: + AwesomeFunctionNamesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + }; + + } // namespace readability + } // namespace tidy + } // namespace clang + + ... + +Constructor of the check receives the ``Name`` and ``Context`` parameters, and +must forward them to the ``ClangTidyCheck`` constructor. + +In our case the check needs to operate on the AST level and it overrides the +``registerMatchers`` and ``check`` methods. If we wanted to analyze code on the +preprocessor level, we'd need instead to override the ``registerPPCallbacks`` +method. + +In the ``registerMatchers`` method we create an AST Matcher (see `AST Matchers`_ +for more information) that will find the pattern in the AST that we want to +inspect. The results of the matching are passed to the ``check`` method, which +can further inspect them and report diagnostics. + +.. code-block:: c++ + + using namespace ast_matchers; + + void AwesomeFunctionNamesCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl().bind("x"), this); + } + + void AwesomeFunctionNamesCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("x"); + if (MatchedDecl->getName().startswith("awesome_")) + return; + diag(MatchedDecl->getLocation(), "function %0 is insufficiently awesome") + << MatchedDecl + << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_"); + } + +(If you want to see an example of a useful check, look at +`clang-tidy/google/ExplicitConstructorCheck.h +`_ +and `clang-tidy/google/ExplicitConstructorCheck.cpp +`_). + + +Registering your Check +---------------------- + +(The ``add_new_check.py`` takes care of registering the check in an existing +module. If you want to create a new module or know the details, read on.) + +The check should be registered in the corresponding module with a distinct name: + +.. code-block:: c++ + + class MyModule : public ClangTidyModule { + public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "my-explicit-constructor"); + } + }; + +Now we need to register the module in the ``ClangTidyModuleRegistry`` using a +statically initialized variable: + +.. code-block:: c++ + + static ClangTidyModuleRegistry::Add X("my-module", + "Adds my lint checks."); + + +When using LLVM build system, we need to use the following hack to ensure the +module is linked into the :program:`clang-tidy` binary: + +Add this near the ``ClangTidyModuleRegistry::Add`` variable: + +.. code-block:: c++ + + // This anchor is used to force the linker to link in the generated object file + // and thus register the MyModule. + volatile int MyModuleAnchorSource = 0; + +And this to the main translation unit of the :program:`clang-tidy` binary (or +the binary you link the ``clang-tidy`` library in) +``clang-tidy/tool/ClangTidyMain.cpp``: + +.. code-block:: c++ + + // This anchor is used to force the linker to link the MyModule. + extern volatile int MyModuleAnchorSource; + static int MyModuleAnchorDestination = MyModuleAnchorSource; + + +Configuring Checks +------------------ + +If a check needs configuration options, it can access check-specific options +using the ``Options.get("SomeOption", DefaultValue)`` call in the check +constructor. In this case the check should also override the +``ClangTidyCheck::storeOptions`` method to make the options provided by the +check discoverable. This method lets :program:`clang-tidy` know which options +the check implements and what the current values are (e.g. for the +``-dump-config`` command line option). + +.. code-block:: c++ + + class MyCheck : public ClangTidyCheck { + const unsigned SomeOption1; + const std::string SomeOption2; + + public: + MyCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + SomeOption(Options.get("SomeOption1", -1U)), + SomeOption(Options.get("SomeOption2", "some default")) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override { + Options.store(Opts, "SomeOption1", SomeOption1); + Options.store(Opts, "SomeOption2", SomeOption2); + } + ... + +Assuming the check is registered with the name "my-check", the option can then +be set in a ``.clang-tidy`` file in the following way: + +.. code-block:: yaml + + CheckOptions: + - key: my-check.SomeOption1 + value: 123 + - key: my-check.SomeOption2 + value: 'some other value' + +If you need to specify check options on a command line, you can use the inline +YAML format: + +.. code-block:: console + + $ clang-tidy -config="{CheckOptions: [{key: a, value: b}, {key: x, value: y}]}" ... + + +Testing Checks +-------------- + +: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 fixits are +applied). In particular, ``CHECK-FIXES:`` can be used to check +that code was not modified by fixits, by checking that it is present +unchanged in the fixed code. The full set of `FileCheck`_ directives +is available (e.g., ``CHECK-MESSAGES-SAME:``, ``CHECK-MESSAGES-NOT:``), though +typically the basic ``CHECK`` forms (``CHECK-MESSAGES`` and ``CHECK-FIXES``) +are sufficient for clang-tidy tests. Note that the `FileCheck`_ +documentation mostly assumes the default prefix (``CHECK``), and hence +describes the directive as ``CHECK:``, ``CHECK-SAME:``, ``CHECK-NOT:``, etc. +Replace ``CHECK`` by either ``CHECK-FIXES`` or ``CHECK-MESSAGES`` for +clang-tidy tests. + +An additional check enabled by ``check_clang_tidy.py`` ensures that +if `CHECK-MESSAGES:` is used in a file then every warning or error +must have an associated CHECK in that file. + +To use the ``check_clang_tidy.py`` script, put a .cpp file with the +appropriate ``RUN`` line in the ``test/clang-tidy`` directory. Use +``CHECK-MESSAGES:`` and ``CHECK-FIXES:`` lines to write checks against +diagnostic messages and fixed code. + +It's advised to make the checks as specific as possible to avoid checks matching +to incorrect parts of the input. Use ``[[@LINE+X]]``/``[[@LINE-X]]`` +substitutions and distinct function and variable names in the test code. + +Here's an example of a test using the ``check_clang_tidy.py`` script (the full +source code is at `test/clang-tidy/google-readability-casting.cpp`_): + +.. code-block:: c++ + + // RUN: %check_clang_tidy %s google-readability-casting %t + + void f(int a) { + int b = (int)a; + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: redundant cast to the same type [google-readability-casting] + // CHECK-FIXES: int b = a; + } + +There are many dark corners in the C++ language, and it may be difficult to make +your check work perfectly in all cases, especially if it issues fix-it hints. The +most frequent pitfalls are macros and templates: + +1. code written in a macro body/template definition may have a different meaning + depending on the macro expansion/template instantiation; +2. multiple macro expansions/template instantiations may result in the same code + being inspected by the check multiple times (possibly, with different + meanings, see 1), and the same warning (or a slightly different one) may be + issued by the check multiple times; :program:`clang-tidy` will deduplicate + _identical_ warnings, but if the warnings are slightly different, all of them + will be shown to the user (and used for applying fixes, if any); +3. making replacements to a macro body/template definition may be fine for some + macro expansions/template instantiations, but easily break some other + expansions/instantiations. + +.. _lit: http://llvm.org/docs/CommandGuide/lit.html +.. _FileCheck: http://llvm.org/docs/CommandGuide/FileCheck.html +.. _test/clang-tidy/google-readability-casting.cpp: http://reviews.llvm.org/diffusion/L/browse/clang-tools-extra/trunk/test/clang-tidy/google-readability-casting.cpp + + +Running clang-tidy on LLVM +-------------------------- + +To test a check it's best to try it out on a larger code base. LLVM and Clang +are the natural targets as you already have the source code around. The most +convenient way to run :program:`clang-tidy` is with a compile command database; +CMake can automatically generate one, for a description of how to enable it see +`How To Setup Tooling For LLVM`_. Once ``compile_commands.json`` is in place and +a working version of :program:`clang-tidy` is in ``PATH`` the entire code base +can be analyzed with ``clang-tidy/tool/run-clang-tidy.py``. The script executes +:program:`clang-tidy` with the default set of checks on every translation unit +in the compile command database and displays the resulting warnings and errors. +The script provides multiple configuration flags. + +* The default set of checks can be overridden using the ``-checks`` argument, + taking the identical format as :program:`clang-tidy` does. For example + ``-checks=-*,modernize-use-override`` will run the ``modernize-use-override`` + check only. + +* To restrict the files examined you can provide one or more regex arguments + that the file names are matched against. + ``run-clang-tidy.py clang-tidy/.*Check\.cpp`` will only analyze clang-tidy + checkers. It may also be necessary to restrict the header files warnings are + displayed from using the ``-header-filter`` flag. It has the same behavior + as the corresponding :program:`clang-tidy` flag. + +* To apply suggested fixes ``-fix`` can be passed as an argument. This gathers + all changes in a temporary directory and applies them. Passing ``-format`` + will run clang-format over changed lines. + diff --git a/docs/clang-tidy/tools/dump_check_docs.py b/docs/clang-tidy/tools/dump_check_docs.py new file mode 100755 index 000000000..a45d15a61 --- /dev/null +++ b/docs/clang-tidy/tools/dump_check_docs.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +r""" +Create stubs for check documentation files. +""" + +import os +import re +import sys + +def main(): + clang_tidy_dir = os.path.normpath( + os.path.join(os.path.dirname(sys.argv[0]), '..', '..', '..', + 'clang-tidy')) + + checks_doc_dir = os.path.normpath( + os.path.join(clang_tidy_dir, '..', 'docs', 'clang-tidy', 'checks')) + + registered_checks = {} + defined_checks = {} + + for dir_name, subdir_list, file_list in os.walk(clang_tidy_dir): + print('Processing directory ' + dir_name + '...') + for file_name in file_list: + full_name = os.path.join(dir_name, file_name) + if file_name.endswith('Module.cpp'): + print('Module ' + file_name) + with open(full_name, 'r') as f: + text = f.read() + for class_name, check_name in re.findall( + r'\.\s*registerCheck\s*<\s*([A-Za-z0-9:]+)\s*>\(\s*"([a-z0-9-]+)"', + text): + registered_checks[check_name] = class_name + elif file_name.endswith('.h'): + print(' ' + file_name + '...') + with open(full_name, 'r') as f: + text = f.read() + for comment, _, _, class_name in re.findall( + r'((([\r\n]//)[^\r\n]*)*)\s+class (\w+)\s*:' + + '\s*public\s+ClangTidyCheck\s*\{', text): + defined_checks[class_name] = comment + + print('Registered checks [%s]: [%s]' % + (len(registered_checks), registered_checks)) + print('Check implementations: %s' % len(defined_checks)) + + checks = registered_checks.keys() + checks.sort() + + for check_name in checks: + doc_file_name = os.path.join(checks_doc_dir, check_name + '.rst') + #if os.path.exists(doc_file_name): + # print('Skipping existing file %s...') + # continue + print('Updating %s...' % doc_file_name) + with open(doc_file_name, 'w') as f: + class_name = re.sub(r'.*:', '', registered_checks[check_name]) + f.write(check_name + '\n' + ('=' * len(check_name)) + '\n\n') + if class_name in defined_checks: + text = defined_checks[class_name] + text = re.sub(r'\n//+ ?(\\brief )?', r'\n', text) + text = re.sub(r'(\n *)\\code\n', r'\1.. code:: c++\n\n', text) + text = re.sub(r'(\n *)\\endcode(\n|$)', r'\n', text) + text = re.sub(r'`', r'``', text) + f.write(text + '\n') + else: + f.write('TODO: add docs\n') + + with open(os.path.join(checks_doc_dir, 'list.rst'), 'w') as f: + f.write( +r"""List of clang-tidy Checks +========================= + +.. toctree:: + """ + '\n '.join(checks)) + + +if __name__ == '__main__': + main() diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..a97a48f0b --- /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 X.Y version. +version = '4' +# The full version, including alpha/beta/rc tags. +release = '4' + +# 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..56e96a676 --- /dev/null +++ b/docs/doxygen.cfg.in @@ -0,0 +1,2300 @@ +# Doxyfile 1.8.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = clang-tools + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @abs_builddir@/doxygen + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = ../.. + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 2 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = NO + +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = \ + @abs_srcdir@/../clang-tidy \ + @abs_srcdir@/../clang-apply-replacements \ + @abs_srcdir@/../clang-query \ + @abs_srcdir@/../clang-rename \ + @abs_srcdir@/../modularize \ + @abs_srcdir@/../pp-trace \ + @abs_srcdir@/../tool-template \ + @abs_srcdir@/doxygen-mainpage.dox + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = clang:: + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = @clang_tools_doxygen_generate_qhp@ + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = @clang_tools_doxygen_qch_filename@ + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = @clang_tools_doxygen_qhp_namespace@ + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = @clang_tools_doxygen_qhp_cust_filter_name@ + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = @clang_tools_doxygen_qhp_cust_filter_attrs@ + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = @clang_tools_doxygen_qhelpgenerator_path@ + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /