diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule AvoidGotoCheck.cpp + ConstCorrectnessCheck.cpp CppCoreGuidelinesTidyModule.cpp InitVariablesCheck.cpp InterfacesGlobalInitCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ConstCorrectnessCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ConstCorrectnessCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ConstCorrectnessCheck.h @@ -0,0 +1,60 @@ +//===--- ConstCorrectnessCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCORRECTNESSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCORRECTNESSCHECK_H + +#include "../ClangTidy.h" +#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" + +namespace clang { +namespace tidy { + +namespace cppcoreguidelines { + +/// This check warns on variables which could be declared const but are not. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-const.html +class ConstCorrectnessCheck : public ClangTidyCheck { +public: + ConstCorrectnessCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AnalyzeValues(Options.get("AnalyzeValues", 1)), + AnalyzeReferences(Options.get("AnalyzeReferences", 1)), + WarnPointersAsValues(Options.get("WarnPointersAsValues", 0)), + TransformValues(Options.get("TransformValues", 1)), + TransformReferences(Options.get("TransformReferences", 1)), + // TransformPointees(Options.get("TransformPointees", 0)), + TransformPointersAsValues(Options.get("TransformPointersAsValues", 0)) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void registerScope(const CompoundStmt *LocalScope, ASTContext *Context); + + using MutationAnalyzer = std::unique_ptr; + llvm::DenseMap ScopesCache; + + const bool AnalyzeValues; + const bool AnalyzeReferences; + const bool WarnPointersAsValues; + + const bool TransformValues; + const bool TransformReferences; + const bool TransformPointees = false; + const bool TransformPointersAsValues; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCORRECTNESSCHECK_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ConstCorrectnessCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ConstCorrectnessCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ConstCorrectnessCheck.cpp @@ -0,0 +1,160 @@ +//===--- ConstCorrectnessCheck.cpp - clang-tidy -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ConstCorrectnessCheck.h" +#include "../utils/FixItHintUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { +// FIXME: This matcher exists in some other code-review as well. +// It should probably move to ASTMatchers. +AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); } +AST_MATCHER_P(DeclStmt, containsDeclaration2, + ast_matchers::internal::Matcher, InnerMatcher) { + return ast_matchers::internal::matchesFirstInPointerRange( + InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder, Builder); +} +AST_MATCHER(ReferenceType, isSpelledAsLValue) { + return Node.isSpelledAsLValue(); +} +} // namespace + +void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AnalyzeValues", AnalyzeValues); + Options.store(Opts, "AnalyzeReferences", AnalyzeReferences); + Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues); + + Options.store(Opts, "TransformValues", TransformValues); + Options.store(Opts, "TransformReferences", TransformReferences); + // FIXME: Pointee analysis not proper yet? + // Options.store(Opts, "TransformPointees", TransformPointees); + Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues); +} + +void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) { + const auto ConstType = hasType(isConstQualified()); + const auto ConstReference = hasType(references(isConstQualified())); + const auto RValueReference = hasType( + referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue())))); + const auto TemplateType = anyOf(hasType(templateTypeParmType()), + hasType(substTemplateTypeParmType())); + + // Match local variables which could be 'const' if not modified later. + // Example: `int i = 10` would match `int i`. + const auto LocalValDecl = varDecl(allOf( + isLocal(), hasInitializer(anything()), + unless(anyOf(ConstType, ConstReference, TemplateType, RValueReference, + hasType(cxxRecordDecl(isLambda())), isImplicit())))); + + // Match the function scope for which the analysis of all local variables + // shall be run. + const auto FunctionScope = functionDecl(hasBody( + compoundStmt(findAll(declStmt(containsDeclaration2( + LocalValDecl.bind("new-local-value"))) + .bind("decl-stmt"))) + .bind("scope"))); + + Finder->addMatcher(FunctionScope, this); +} + +/// Classify for a variable in what the Const-Check is interested. +enum class VariableCategory { Value, Reference, Pointer }; + +void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) { + const auto *LocalScope = Result.Nodes.getNodeAs("scope"); + assert(LocalScope && "Did not match scope for local variable"); + registerScope(LocalScope, Result.Context); + + const auto *Variable = Result.Nodes.getNodeAs("new-local-value"); + assert(Variable && "Did not match local variable definition"); + + VariableCategory VC = VariableCategory::Value; + if (Variable->getType()->isReferenceType()) + VC = VariableCategory::Reference; + if (Variable->getType()->isPointerType()) + VC = VariableCategory::Pointer; + + // Each variable can only in one category: Value, Pointer, Reference. + // Analysis can be controlled for every category. + if (VC == VariableCategory::Reference && !AnalyzeReferences) + return; + + if (VC == VariableCategory::Pointer && !WarnPointersAsValues) + return; + + if (VC == VariableCategory::Value && !AnalyzeValues) + return; + + // Offload const-analysis to utility function. + if (ScopesCache[LocalScope]->isMutated(Variable)) + return; + + auto Diag = diag(Variable->getBeginLoc(), + "variable %0 of type %1 can be declared 'const'") + << Variable << Variable->getType(); + + const auto *VarDeclStmt = Result.Nodes.getNodeAs("decl-stmt"); + + // It can not be guaranteed that the variable is declared isolated, therefore + // a transformation might effect the other variables as well and be incorrect. + if (VarDeclStmt == nullptr || !VarDeclStmt->isSingleDecl()) + return; + + using namespace utils::fixit; + using llvm::Optional; + if (VC == VariableCategory::Value && TransformValues) { + if (Optional Fix = addQualifierToVarDecl( + *Variable, DeclSpec::TQ_const, QualifierTarget::Value, + QualifierPolicy::Right, Result.Context)) + Diag << *Fix; + return; + } + + if (VC == VariableCategory::Reference && TransformReferences) { + if (Optional Fix = addQualifierToVarDecl( + *Variable, DeclSpec::TQ_const, QualifierTarget::Value, + QualifierPolicy::Right, Result.Context)) + Diag << *Fix; + return; + } + + if (VC == VariableCategory::Pointer) { + if (WarnPointersAsValues && TransformPointersAsValues) { + if (Optional Fix = addQualifierToVarDecl( + *Variable, DeclSpec::TQ_const, QualifierTarget::Value, + QualifierPolicy::Right, Result.Context)) + Diag << *Fix; + } + if (TransformPointees) { + if (Optional Fix = addQualifierToVarDecl( + *Variable, DeclSpec::TQ_const, QualifierTarget::Pointee, + QualifierPolicy::Right, Result.Context)) + Diag << *Fix; + } + return; + } +} + +void ConstCorrectnessCheck::registerScope(const CompoundStmt *LocalScope, + ASTContext *Context) { + if (ScopesCache.find(LocalScope) == ScopesCache.end()) + ScopesCache.insert(std::make_pair( + LocalScope, + std::make_unique(*LocalScope, *Context))); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -15,6 +15,7 @@ #include "../modernize/UseOverrideCheck.h" #include "../readability/MagicNumbersCheck.h" #include "AvoidGotoCheck.h" +#include "ConstCorrectnessCheck.h" #include "InitVariablesCheck.h" #include "InterfacesGlobalInitCheck.h" #include "MacroUsageCheck.h" @@ -48,6 +49,8 @@ "cppcoreguidelines-avoid-goto"); CheckFactories.registerCheck( "cppcoreguidelines-avoid-magic-numbers"); + CheckFactories.registerCheck( + "cppcoreguidelines-const-correctness"); CheckFactories.registerCheck( "cppcoreguidelines-explicit-virtual-functions"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp @@ -78,7 +78,7 @@ "copy in each iteration; consider making this a reference") << utils::fixit::changeVarDeclToReference(LoopVar, Context); if (!LoopVar.getType().isConstQualified()) - Diagnostic << utils::fixit::changeVarDeclToConst(LoopVar); + Diagnostic << *utils::fixit::addQualifierToVarDecl(LoopVar, DeclSpec::TQ::TQ_const); return true; } @@ -104,7 +104,7 @@ 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::addQualifierToVarDecl(LoopVar, DeclSpec::TQ::TQ_const) << utils::fixit::changeVarDeclToReference(LoopVar, Context); return true; } diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp @@ -22,7 +22,7 @@ DiagnosticBuilder &Diagnostic) { Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); if (!Var.getType().isLocalConstQualified()) - Diagnostic << utils::fixit::changeVarDeclToConst(Var); + Diagnostic << *utils::fixit::addQualifierToVarDecl(Var, DeclSpec::TQ::TQ_const); } } // namespace diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.cpp @@ -163,14 +163,15 @@ // whether it is const or not as constness can differ between definition and // declaration. if (!CurrentParam.getType().getCanonicalType().isConstQualified()) - Diag << utils::fixit::changeVarDeclToConst(CurrentParam); + Diag << *utils::fixit::addQualifierToVarDecl(CurrentParam, + DeclSpec::TQ::TQ_const); } } void UnnecessaryValueParamCheck::registerPPCallbacks( const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { - Inserter = std::make_unique(SM, getLangOpts(), - IncludeStyle); + Inserter = + std::make_unique(SM, getLangOpts(), IncludeStyle); PP->addPPCallbacks(Inserter->CreatePPCallbacks()); } diff --git a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h --- a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h +++ b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.h @@ -11,6 +11,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/Sema/DeclSpec.h" namespace clang { namespace tidy { @@ -23,6 +24,35 @@ /// Creates fix to make ``VarDecl`` const qualified. FixItHint changeVarDeclToConst(const VarDecl &Var); +/// This enum defines where the 'const' shall be preferably added. +enum class QualifierPolicy { + Left, // Add the `const` always to the left side, if that is possible. + Right, // Add the `const` always to the right side. +}; + +/// This enum defines which entity is the target for adding the 'const'. This +/// makes only a difference for pointer-types. Other types behave identical +/// for either value of \c ConstTarget. +enum class QualifierTarget { + Pointee, /// Transforming a pointer goes for the pointee and not the pointer + /// itself. For references and normal values this option has no + /// effect. + /// `int * p = &i;` -> `const int * p = &i` or `int const * p = &i`. + Value, /// Transforming pointers will consider the pointer itself. + /// `int * p = &i;` -> `int * const = &i` +}; + +/// \brief Creates fix to make ``VarDecl`` const qualified. Only valid if +/// `Var` is isolated in written code. `int foo = 42;` +/// +/// If the 'FixItHint' would be applied inside a macro or at an invalid +/// \c SourceLocation it is not returned. +Optional +addQualifierToVarDecl(const VarDecl &Var, DeclSpec::TQ Qualifier, + QualifierTarget CT = QualifierTarget::Pointee, + QualifierPolicy CP = QualifierPolicy::Left, + const ASTContext *Context = nullptr); + } // namespace fixit } // namespace utils } // namespace tidy diff --git a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp --- a/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp @@ -26,10 +26,209 @@ return FixItHint::CreateInsertion(AmpLocation, "&"); } -FixItHint changeVarDeclToConst(const VarDecl &Var) { - return FixItHint::CreateInsertion(Var.getTypeSpecStartLoc(), "const "); +static bool isValueType(const Type *T) { + return !(isa(T) || isa(T) || isa(T) || + isa(T)); } +static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); } +static bool isArrayType(QualType QT) { return isa(QT.getTypePtr()); } +static bool isReferenceType(QualType QT) { + return isa(QT.getTypePtr()); +} +static bool isPointerType(const Type *T) { return isa(T); } +static bool isPointerType(QualType QT) { + return isPointerType(QT.getTypePtr()); +} +static bool isMemberOrFunctionPointer(QualType QT) { + return (isPointerType(QT) && QT->isFunctionPointerType()) || + isa(QT.getTypePtr()); +} +static bool locDangerous(SourceLocation S) { + return S.isInvalid() || S.isMacroID(); +} + +static Optional +skipLParensBackwards(SourceLocation Start, const ASTContext &Context) { + Token T; + auto PreviousTokenLParen = [&]() { + T = lexer::getPreviousToken(Start, Context.getSourceManager(), + Context.getLangOpts()); + return T.is(tok::l_paren); + }; + while (PreviousTokenLParen()) { + if (locDangerous(Start)) + return None; + Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(), + Context.getLangOpts()); + } + if (locDangerous(Start)) + return None; + return Start; +} + +static Optional fixIfNotDangerous(SourceLocation Loc, + StringRef Text) { + if (locDangerous(Loc)) + return None; + return FixItHint::CreateInsertion(Loc, Text); +} + +// Build a string that can be emitted as FixIt with either a space in before +// or after the qualifier, either ' const' or 'const '. +static std::string buildQualifier(DeclSpec::TQ Qualifier, + bool WhitespaceBefore = false) { + if (WhitespaceBefore) + return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier)).str(); + return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier)) + llvm::Twine(' ')) + .str(); +} + +static Optional changeValue(const VarDecl &Var, + DeclSpec::TQ Qualifier, + QualifierTarget QualTarget, + QualifierPolicy QualPolicy, + const ASTContext &Context) { + switch (QualPolicy) { + case QualifierPolicy::Left: + return fixIfNotDangerous(Var.getTypeSpecStartLoc(), + buildQualifier(Qualifier)); + case QualifierPolicy::Right: + Optional IgnoredParens = + skipLParensBackwards(Var.getLocation(), Context); + + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); + return None; + } +} + +static Optional changePointerItself(const VarDecl &Var, + DeclSpec::TQ Qualifier, + const ASTContext &Context) { + if (locDangerous(Var.getLocation())) + return None; + + Optional IgnoredParens = + skipLParensBackwards(Var.getLocation(), Context); + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); + return None; +} + +static Optional +changePointer(const VarDecl &Var, DeclSpec::TQ Qualifier, const Type *Pointee, + QualifierTarget QualTarget, QualifierPolicy QualPolicy, + const ASTContext &Context) { + // The pointer itself shall be marked as `const`. This is always to the right + // of the '*' or in front of the identifier. + if (QualTarget == QualifierTarget::Value) + return changePointerItself(Var, Qualifier, Context); + + // Mark the pointee `const` that is a normal value (`int* p = nullptr;`). + if (QualTarget == QualifierTarget::Pointee && isValueType(Pointee)) { + // Adding the `const` on the left side is just the beginning of the type + // specification. (`const int* p = nullptr;`) + if (QualPolicy == QualifierPolicy::Left) + return fixIfNotDangerous(Var.getTypeSpecStartLoc(), + buildQualifier(Qualifier)); + + // Adding the `const` on the right side of the value type requires finding + // the `*` token and placing the `const` left of it. + // (`int const* p = nullptr;`) + if (QualPolicy == QualifierPolicy::Right) { + SourceLocation BeforeStar = lexer::findPreviousTokenKind( + Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), + tok::star); + if (locDangerous(BeforeStar)) + return None; + Optional IgnoredParens = + skipLParensBackwards(BeforeStar, Context); + + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, + buildQualifier(Qualifier, true)); + return None; + } + } + + if (QualTarget == QualifierTarget::Pointee && isPointerType(Pointee)) { + // Adding the `const` to the pointee if the pointee is a pointer + // is the same as 'QualPolicy == Right && isValueType(Pointee)'. + // The `const` must be left of the last `*` token. + // (`int * const* p = nullptr;`) + SourceLocation BeforeStar = lexer::findPreviousTokenKind( + Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), + tok::star); + return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true)); + } + + llvm_unreachable("All paths should have been handled"); +} + +static Optional +changeReferencee(const VarDecl &Var, DeclSpec::TQ Qualifier, QualType Pointee, + QualifierTarget QualTarget, QualifierPolicy QualPolicy, + const ASTContext &Context) { + if (QualPolicy == QualifierPolicy::Left && isValueType(Pointee)) + return fixIfNotDangerous(Var.getTypeSpecStartLoc(), + buildQualifier(Qualifier)); + + SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind( + Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), + tok::amp, tok::ampamp); + Optional IgnoredParens = + skipLParensBackwards(BeforeRef, Context); + if (IgnoredParens) + return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true)); + + return None; +} + +Optional addQualifierToVarDecl(const VarDecl &Var, + DeclSpec::TQ Qualifier, + QualifierTarget QualTarget, + QualifierPolicy QualPolicy, + const ASTContext *Context) { + assert((QualPolicy == QualifierPolicy::Left || + QualPolicy == QualifierPolicy::Right) && + "Unexpected Insertion Policy"); + assert((QualTarget == QualifierTarget::Pointee || + QualTarget == QualifierTarget::Value) && + "Unexpected Target"); + + QualType ParenStrippedType = Var.getType().IgnoreParens(); + if (isValueType(ParenStrippedType)) + return changeValue(Var, Qualifier, QualTarget, QualPolicy, *Context); + + if (isReferenceType(ParenStrippedType)) + return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(), + QualTarget, QualPolicy, *Context); + + if (isMemberOrFunctionPointer(ParenStrippedType)) + return changePointerItself(Var, Qualifier, *Context); + + if (isPointerType(ParenStrippedType)) + return changePointer(Var, Qualifier, + ParenStrippedType->getPointeeType().getTypePtr(), + QualTarget, QualPolicy, *Context); + + if (isArrayType(ParenStrippedType)) { + const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe(); + assert(AT && "Did not retrieve array element type for an array."); + + if (isValueType(AT)) + return changeValue(Var, Qualifier, QualTarget, QualPolicy, *Context); + + if (isPointerType(AT)) + return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(), + QualTarget, QualPolicy, *Context); + } + + return {}; + llvm_unreachable( + "All possible combinations should have been handled already"); +} } // namespace fixit } // namespace utils } // namespace tidy diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -39,6 +39,8 @@ const SourceManager &SM, const LangOptions &LangOpts, TokenKind TK, TokenKinds... TKs) { + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); while (true) { SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); if (L.isInvalid() || L.isMacroID()) @@ -46,7 +48,7 @@ Token T; // Returning 'true' is used to signal failure to retrieve the token. - if (Lexer::getRawToken(L, T, SM, LangOpts)) + if (Lexer::getRawToken(L, T, SM, LangOpts, /*IgnoreWhiteSpace=*/true)) return SourceLocation(); if (T.isOneOf(TK, TKs...)) diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp @@ -47,6 +47,9 @@ const SourceManager &SM, const LangOptions &LangOpts, tok::TokenKind TK) { + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); + while (true) { SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); if (L.isInvalid() || L.isMacroID()) diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -102,6 +102,11 @@ - New :doc:`cppcoreguidelines-init-variables ` check. +- New :doc:`cppcoreguidelines-const-correctness + ` check. + + Suggest adding ``const`` to unmodified local variables. + - New :doc:`darwin-dispatch-once-nonstatic ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-const-correctness.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-const-correctness.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-const-correctness.rst @@ -0,0 +1,68 @@ +.. title:: clang-tidy - cppcoreguidelines-const-correctness + +cppcoreguidelines-const-correctness +=================================== + +This check implements detection of local variables which could be declared as +``const``, but are not. Declaring variables as ``const`` is required by many +coding guidelines, such as: +`CppCoreGuidelines ES.25 `_ +and `High Integrity C++ 7.1.2 `_. + +Please note that this analysis is type-based only. Variables that are not modified +but non-const handles might escape out of the scope are not diagnosed as potential +``const``. + +.. code-block:: c++ + + // Declare a variable, which is not ``const`` ... + int i = 42; + // but use it as read-only. This means that `i` can be declared ``const``. + int result = i * i; + +The check analyzes values, pointers and references (if configured that way). +For better understanding some code samples: + +.. code-block:: c++ + + // Normal values like built-ins or objects. + int potential_const_int = 42; + int copy_of_value = potential_const_int; + + MyClass could_be_const; + could_be_const.const_qualified_method(); + + // References can be declared const as well. + int &reference_value = potential_const_int; + int another_copy = reference_value; + + // Similar behaviour for pointers. + int *pointer_variable = &potential_const_int; + int last_copy = *pointer_variable; + + +Options +------- + +.. option:: AnalyzeValues (default = 1) + + Enable or disable the analysis of ordinary value variables, like ``int i = 42;`` + +.. option:: AnalyzeReferences (default = 1) + + Enable or disable the analysis of reference variables, like ``int &ref = i;`` + +.. option:: WarnPointersAsValues (default = 0) + + This option enables the suggestion for ``const`` of the pointer itself. + Pointer values have two possibilities to be ``const``, the pointer itself + and the value pointing to. + + .. code-block:: c++ + + const int value = 42; + const int * const pointer_variable = &value; + + // The following operations are forbidden for `pointer_variable`. + // *pointer_variable = 44; + // pointer_variable = nullptr; diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -195,6 +195,7 @@ cppcoreguidelines-avoid-goto cppcoreguidelines-avoid-magic-numbers (redirects to readability-magic-numbers) cppcoreguidelines-c-copy-assignment-signature (redirects to misc-unconventional-assign-operator) + cppcoreguidelines-const-correctness cppcoreguidelines-explicit-virtual-functions (redirects to modernize-use-override) cppcoreguidelines-init-variables cppcoreguidelines-interfaces-global-init diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-pointer-as-values.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-pointer-as-values.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-pointer-as-values.cpp @@ -0,0 +1,11 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-const-correctness %t \ +// RUN: -config='{CheckOptions: \ +// RUN: [{key: "cppcoreguidelines-const-correctness.AnalyzeValues", value: 1},\ +// RUN: {key: "cppcoreguidelines-const-correctness.WarnPointersAsValues", value: 1}]}' \ +// RUN: -- + +void potential_const_pointer() { + double np_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + double *p_local0 = &np_local0[1]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double *' can be declared 'const' +} diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-transform-pointer-as-values.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-transform-pointer-as-values.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-transform-pointer-as-values.cpp @@ -0,0 +1,13 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-const-correctness %t \ +// RUN: -config='{CheckOptions: \ +// RUN: [{key: "cppcoreguidelines-const-correctness.AnalyzeValues", value: 1},\ +// RUN: {key: "cppcoreguidelines-const-correctness.WarnPointersAsValues", value: 1}, \ +// RUN: {key: "cppcoreguidelines-const-correctness.TransformPointersAsValues", value: 1},\ +// RUN: ]}' -- + +void potential_const_pointer() { + double np_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + double *p_local0 = &np_local0[1]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double *' can be declared 'const' + // CHECK-FIXES: const +} diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-transform-values.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-transform-values.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-transform-values.cpp @@ -0,0 +1,166 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-const-correctness %t -- \ +// RUN: -config="{CheckOptions: [\ +// RUN: {key: 'cppcoreguidelines-const-correctness.TransformValues', value: 1},\ +// RUN: ]}" -- + +bool global; +char np_global = 0; // globals can't be known to be const + +namespace foo { +int scoped; +float np_scoped = 1; // namespace variables are like globals +} // namespace foo + +// Lambdas should be ignored, because they do not follow the normal variable +// semantic (e.g. the type is only known to the compiler). +void lambdas() { + auto Lambda = [](int i) { return i < 0; }; +} + +void some_function(double, wchar_t); + +void some_function(double np_arg0, wchar_t np_arg1) { + int p_local0 = 2; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + // CHECK-FIXES: const +} + +void nested_scopes() { + { + int p_local1 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: variable 'p_local1' of type 'int' can be declared 'const' + // CHECK-FIXES: const + } +} + +template +void define_locals(T np_arg0, T &np_arg1, int np_arg2) { + T p_local0 = 0; + int p_local1 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared 'const' + // CHECK-FIXES: const +} + +void template_instantiation() { + const int np_local0 = 42; + int np_local1 = 42; + + define_locals(np_local0, np_local1, np_local0); + define_locals(np_local1, np_local1, np_local1); +} + +struct ConstNonConstClass { + ConstNonConstClass(); + ConstNonConstClass(double &np_local0); + double nonConstMethod() {} + double constMethod() const {} + double modifyingMethod(double &np_arg0) const; + + double NonConstMember; + const double ConstMember; + + double &NonConstMemberRef; + const double &ConstMemberRef; + + double *NonConstMemberPtr; + const double *ConstMemberPtr; +}; + +void direct_class_access() { + ConstNonConstClass p_local0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass' can be declared 'const' + // CHECK-FIXES: const + p_local0.constMethod(); +} + +void class_access_array() { + ConstNonConstClass p_local0[2]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass [2]' can be declared 'const' + // CHECK-FIXES: const + p_local0[0].constMethod(); +} + +struct MyVector { + double *begin(); + const double *begin() const; + + double *end(); + const double *end() const; + + double &operator[](int index); + double operator[](int index) const; + + double values[100]; +}; + +void vector_usage() { + double p_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double [10]' can be declared 'const' + // CHECK-FIXES: const +} + +void range_for() { + int np_local0[2] = {1, 2}; + int *np_local3[2] = {&np_local0[0], &np_local0[1]}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local3' of type 'int *[2]' can be declared 'const' + // CHECK-FIXES: const + for (int *non_const_ptr : np_local3) { + *non_const_ptr = 45; + } +} + +void casts() { + decltype(sizeof(void *)) p_local0 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'decltype(sizeof(void *))' (aka 'unsigned long') can be declared 'const' + // CHECK-FIXES: const +} + +// taken from http://www.cplusplus.com/reference/type_traits/integral_constant/ +template +struct integral_constant { + static constexpr T value = v; + using value_type = T; + using type = integral_constant; + constexpr operator T() { return v; } +}; + +template +struct is_integral : integral_constant {}; +template <> +struct is_integral : integral_constant {}; + +template +struct not_integral : integral_constant {}; +template <> +struct not_integral : integral_constant {}; + +// taken from http://www.cplusplus.com/reference/type_traits/enable_if/ +template +struct enable_if {}; + +template +struct enable_if { using type = T; }; + +template +struct TMPClass { + T alwaysConst() const { return T{}; } + + template ::value>::type> + T sometimesConst() const { return T{}; } + + template ::value>::type> + T sometimesConst() { return T{}; } +}; + +void meta_type() { + TMPClass p_local0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'TMPClass' can be declared 'const' + // CHECK-FIXES: const + p_local0.alwaysConst(); + p_local0.sometimesConst(); + + TMPClass p_local1; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'TMPClass' can be declared 'const' + // CHECK-FIXES: const + p_local1.alwaysConst(); +} diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-values.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-values.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-const-correctness-values.cpp @@ -0,0 +1,563 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-const-correctness %t + +// ------- Provide test samples for primitive builtins --------- +// - every 'p_*' variable is a 'potential_const_*' variable +// - every 'np_*' variable is a 'non_potential_const_*' variable + +bool global; +char np_global = 0; // globals can't be known to be const + +namespace foo { +int scoped; +float np_scoped = 1; // namespace variables are like globals +} // namespace foo + +// Lambdas should be ignored, because they do not follow the normal variable +// semantic (e.g. the type is only known to the compiler). +void lambdas() { + auto Lambda = [](int i) { return i < 0; }; +} + +void some_function(double, wchar_t); + +void some_function(double np_arg0, wchar_t np_arg1) { + int p_local0 = 2; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + + int np_local0; + const int np_local1 = 42; + + unsigned int np_local2 = 3; + np_local2 <<= 4; + + int np_local3 = 4; + ++np_local3; + int np_local4 = 4; + np_local4++; + + int np_local5 = 4; + --np_local5; + int np_local6 = 4; + np_local6--; +} + +void nested_scopes() { + int p_local0 = 2; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + int np_local0 = 42; + + { + int p_local1 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:5: warning: variable 'p_local1' of type 'int' can be declared 'const' + np_local0 *= 2; + } +} + +void some_lambda_environment_capture_all_by_reference(double np_arg0) { + int np_local0 = 0; + int p_local0 = 1; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + + int np_local2; + const int np_local3 = 2; + + // Capturing all variables by reference prohibits making them const. + [&]() { ++np_local0; }; + + int p_local1 = 0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared 'const' +} + +void some_lambda_environment_capture_all_by_value(double np_arg0) { + int np_local0 = 0; + int p_local0 = 1; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + + int np_local1; + const int np_local2 = 2; + + // Capturing by value has no influence on them. + [=]() { (void)p_local0; }; + + np_local0 += 10; +} + +void function_inout_pointer(int *inout); +void function_in_pointer(const int *in); + +void some_pointer_taking(int *out) { + int np_local0 = 42; + const int *const p0_np_local0 = &np_local0; + int *const p1_np_local0 = &np_local0; + + int np_local1 = 42; + const int *const p0_np_local1 = &np_local1; + int *const p1_np_local1 = &np_local1; + *p1_np_local0 = 43; + + int np_local2 = 42; + function_inout_pointer(&np_local2); + + // Prevents const. + int np_local3 = 42; + out = &np_local3; // This returns and invalid address, its just about the AST + + int p_local1 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared 'const' + const int *const p0_p_local1 = &p_local1; + + int p_local2 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'int' can be declared 'const' + function_in_pointer(&p_local2); +} + +void function_inout_ref(int &inout); +void function_in_ref(const int &in); + +void some_reference_taking() { + int np_local0 = 42; + const int &r0_np_local0 = np_local0; + int &r1_np_local0 = np_local0; + r1_np_local0 = 43; + const int &r2_np_local0 = r1_np_local0; + + int np_local1 = 42; + function_inout_ref(np_local1); + + int p_local0 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + const int &r0_p_local0 = p_local0; + + int p_local1 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared 'const' + function_in_ref(p_local1); +} + +double *non_const_pointer_return() { + double p_local0 = 0.0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared 'const' + double np_local0 = 24.4; + + return &np_local0; +} + +const double *const_pointer_return() { + double p_local0 = 0.0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared 'const' + double p_local1 = 24.4; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double' can be declared 'const' + return &p_local1; +} + +double &non_const_ref_return() { + double p_local0 = 0.0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared 'const' + double np_local0 = 42.42; + return np_local0; +} + +const double &const_ref_return() { + double p_local0 = 0.0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double' can be declared 'const' + double p_local1 = 24.4; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double' can be declared 'const' + return p_local1; +} + +double *&return_non_const_pointer_ref() { + double *np_local0 = nullptr; + return np_local0; +} + +void overloaded_arguments(const int &in); +void overloaded_arguments(int &inout); +void overloaded_arguments(const int *in); +void overloaded_arguments(int *inout); + +void function_calling() { + int np_local0 = 42; + overloaded_arguments(np_local0); + + const int np_local1 = 42; + overloaded_arguments(np_local1); + + int np_local2 = 42; + overloaded_arguments(&np_local2); + + const int np_local3 = 42; + overloaded_arguments(&np_local3); +} + +template +void define_locals(T np_arg0, T &np_arg1, int np_arg2) { + T np_local0 = 0; + np_local0 += np_arg0 * np_arg1; + + T np_local1 = 42; + np_local0 += np_local1; + + // Used as argument to an overloaded function with const and non-const. + T np_local2 = 42; + overloaded_arguments(np_local2); + + int np_local4 = 42; + // non-template values are ok still. + int p_local0 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + np_local4 += p_local0; +} + +void template_instantiation() { + const int np_local0 = 42; + int np_local1 = 42; + + define_locals(np_local0, np_local1, np_local0); + define_locals(np_local1, np_local1, np_local1); +} + +struct ConstNonConstClass { + ConstNonConstClass(); + ConstNonConstClass(double &np_local0); + double nonConstMethod() {} + double constMethod() const {} + double modifyingMethod(double &np_arg0) const; + + double NonConstMember; + const double ConstMember; + + double &NonConstMemberRef; + const double &ConstMemberRef; + + double *NonConstMemberPtr; + const double *ConstMemberPtr; +}; + +void direct_class_access() { + ConstNonConstClass np_local0; + + np_local0.constMethod(); + np_local0.nonConstMethod(); + + ConstNonConstClass p_local0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass' can be declared 'const' + p_local0.constMethod(); + + ConstNonConstClass p_local1; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'ConstNonConstClass' can be declared 'const' + double np_local1; + p_local1.modifyingMethod(np_local1); + + double np_local2; + ConstNonConstClass p_local2(np_local2); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'ConstNonConstClass' can be declared 'const' + + ConstNonConstClass np_local3; + np_local3.NonConstMember = 42.; + + ConstNonConstClass np_local4; + np_local4.NonConstMemberRef = 42.; + + ConstNonConstClass p_local3; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'ConstNonConstClass' can be declared 'const' + const double val0 = p_local3.NonConstMember; + const double val1 = p_local3.NonConstMemberRef; + const double val2 = *p_local3.NonConstMemberPtr; + + ConstNonConstClass p_local4; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local4' of type 'ConstNonConstClass' can be declared 'const' + *np_local4.NonConstMemberPtr = 42.; +} + +void class_access_array() { + ConstNonConstClass np_local0[2]; + np_local0[0].constMethod(); + np_local0[1].constMethod(); + np_local0[1].nonConstMethod(); + + ConstNonConstClass p_local0[2]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'ConstNonConstClass [2]' can be declared 'const' + p_local0[0].constMethod(); + np_local0[1].constMethod(); +} + +struct OperatorsAsConstAsPossible { + OperatorsAsConstAsPossible &operator+=(const OperatorsAsConstAsPossible &rhs); + OperatorsAsConstAsPossible operator+(const OperatorsAsConstAsPossible &rhs) const; +}; + +struct NonConstOperators { +}; +NonConstOperators operator+(NonConstOperators &lhs, NonConstOperators &rhs); +NonConstOperators operator-(NonConstOperators lhs, NonConstOperators rhs); + +void internal_operator_calls() { + OperatorsAsConstAsPossible np_local0; + OperatorsAsConstAsPossible np_local1; + OperatorsAsConstAsPossible p_local0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'OperatorsAsConstAsPossible' can be declared 'const' + OperatorsAsConstAsPossible p_local1; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'OperatorsAsConstAsPossible' can be declared 'const' + + np_local0 += p_local0; + np_local1 = p_local0 + p_local1; + + NonConstOperators np_local2; + NonConstOperators np_local3; + NonConstOperators np_local4; + + np_local2 = np_local3 + np_local4; + + NonConstOperators p_local2; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'NonConstOperators' can be declared 'const' + NonConstOperators p_local3 = p_local2 - p_local2; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'NonConstOperators' can be declared 'const' +} + +struct MyVector { + double *begin(); + const double *begin() const; + + double *end(); + const double *end() const; + + double &operator[](int index); + double operator[](int index) const; + + double values[100]; +}; + +void vector_usage() { + double np_local0[10]; + np_local0[5] = 42.; + + MyVector np_local1; + np_local1[5] = 42.; + + double p_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'double [10]' can be declared 'const' + double p_local1 = p_local0[5]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double' can be declared 'const' + + // The following subscript calls suprisingly choose the non-const operator + // version. + MyVector np_local2; + double p_local2 = np_local2[42]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'double' can be declared 'const' + + MyVector np_local3; + const double np_local4 = np_local3[42]; + + // This subscript results in const overloaded operator. + const MyVector np_local5{}; + double p_local3 = np_local5[42]; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'double' can be declared 'const' +} + +void const_handle(const double &np_local0); +void const_handle(const double *np_local0); + +void non_const_handle(double &np_local0); +void non_const_handle(double *np_local0); + +void handle_from_array() { + // Non-const handle from non-const array forbids declaring the array as const + double np_local0[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + double *p_local0 = &np_local0[1]; // Could be `double *const`, but warning deactivated by default + + double np_local1[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + double &non_const_ref = np_local1[1]; + non_const_ref = 42.; + + double np_local2[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + double *np_local3; + np_local3 = &np_local2[5]; + + double np_local4[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + non_const_handle(np_local4[2]); + double np_local5[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + non_const_handle(&np_local5[2]); + + // Constant handles are ok + double p_local1[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'double [10]' can be declared 'const' + const double *p_local2 = &p_local1[2]; // Could be `const double *const`, but warning deactivated by default + + double p_local3[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'double [10]' can be declared 'const' + const double &const_ref = p_local3[2]; + + double p_local4[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local4' of type 'double [10]' can be declared 'const' + const double *const_ptr; + const_ptr = &p_local4[2]; + + double p_local5[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local5' of type 'double [10]' can be declared 'const' + const_handle(p_local5[2]); + double p_local6[10] = {0., 1., 2., 3., 4., 5., 6., 7., 8., 9.}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local6' of type 'double [10]' can be declared 'const' + const_handle(&p_local6[2]); +} + +void range_for() { + int np_local0[2] = {1, 2}; + for (int &non_const_ref : np_local0) { + non_const_ref = 42; + } + + int np_local1[2] = {1, 2}; + for (auto &non_const_ref : np_local1) { + non_const_ref = 43; + } + + int np_local2[2] = {1, 2}; + for (auto &&non_const_ref : np_local2) { + non_const_ref = 44; + } + + // FIXME the warning message is suboptimal. It could be defined as + // `int *const np_local3[2]` because the pointers are not reseated. + // But this is not easily deducable from the warning. + int *np_local3[2] = {&np_local0[0], &np_local0[1]}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'np_local3' of type 'int *[2]' can be declared 'const' + for (int *non_const_ptr : np_local3) { + *non_const_ptr = 45; + } + + // FIXME same as above, but silenced + int *const np_local4[2] = {&np_local0[0], &np_local0[1]}; + for (auto *non_const_ptr : np_local4) { + *non_const_ptr = 46; + } + + int p_local0[2] = {1, 2}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int [2]' can be declared 'const' + for (int value : p_local0) { + // CHECK-MESSAGES: [[@LINE-1]]:8: warning: variable 'value' of type 'int' can be declared 'const' + } + + int p_local1[2] = {1, 2}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int [2]' can be declared 'const' + for (const int &const_ref : p_local1) { + } + + int *p_local2[2] = {&np_local0[0], &np_local0[1]}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'int *[2]' can be declared 'const' + for (const int *con_ptr : p_local2) { + } + + int *p_local3[2] = {nullptr, nullptr}; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local3' of type 'int *[2]' can be declared 'const' + for (const auto *con_ptr : p_local3) { + } +} + +inline void *operator new(decltype(sizeof(void *)), void *p) { return p; } + +struct Value { +}; +void placement_new() { + Value Mem; + Value *V = new (&Mem) Value; +} + +struct ModifyingConversion { + operator int() { return 15; } +}; +struct NonModifyingConversion { + operator int() const { return 15; } +}; +void conversion_operators() { + ModifyingConversion np_local0; + NonModifyingConversion p_local0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'NonModifyingConversion' can be declared 'const' + + int np_local1 = np_local0; + np_local1 = p_local0; +} + +void casts() { + decltype(sizeof(void *)) p_local0 = 42; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'decltype(sizeof(void *))' (aka 'unsigned long') can be declared 'const' + auto np_local0 = reinterpret_cast(p_local0); + np_local0 = nullptr; + + int p_local1 = 43; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'int' can be declared 'const' + short p_local2 = static_cast(p_local1); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local2' of type 'short' can be declared 'const' + + int np_local1 = p_local2; + int &np_local2 = static_cast(np_local1); + np_local2 = 5; +} + +void ternary_operator() { + int np_local0 = 1, np_local1 = 2; + int &np_local2 = true ? np_local0 : np_local1; + np_local2 = 2; + + int p_local0 = 3, np_local3 = 5; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'int' can be declared 'const' + const int &np_local4 = true ? p_local0 : ++np_local3; + + int np_local5[3] = {1, 2, 3}; + int &np_local6 = np_local5[1] < np_local5[2] ? np_local5[0] : np_local5[2]; + np_local6 = 42; + + int np_local7[3] = {1, 2, 3}; + int *np_local8 = np_local7[1] < np_local7[2] ? &np_local7[0] : &np_local7[2]; + *np_local8 = 42; +} + +// taken from http://www.cplusplus.com/reference/type_traits/integral_constant/ +template +struct integral_constant { + static constexpr T value = v; + using value_type = T; + using type = integral_constant; + constexpr operator T() { return v; } +}; + +template +struct is_integral : integral_constant {}; +template <> +struct is_integral : integral_constant {}; + +template +struct not_integral : integral_constant {}; +template <> +struct not_integral : integral_constant {}; + +// taken from http://www.cplusplus.com/reference/type_traits/enable_if/ +template +struct enable_if {}; + +template +struct enable_if { using type = T; }; + +template +struct TMPClass { + T alwaysConst() const { return T{}; } + + template ::value>::type> + T sometimesConst() const { return T{}; } + + template ::value>::type> + T sometimesConst() { return T{}; } +}; + +void meta_type() { + TMPClass p_local0; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local0' of type 'TMPClass' can be declared 'const' + p_local0.alwaysConst(); + p_local0.sometimesConst(); + + TMPClass p_local1; + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: variable 'p_local1' of type 'TMPClass' can be declared 'const' + p_local1.alwaysConst(); + + TMPClass np_local0; + np_local0.alwaysConst(); + np_local0.sometimesConst(); +} diff --git a/clang-tools-extra/unittests/clang-tidy/AddConstTest.cpp b/clang-tools-extra/unittests/clang-tidy/AddConstTest.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/unittests/clang-tidy/AddConstTest.cpp @@ -0,0 +1,982 @@ +#include "../clang-tidy/utils/FixItHintUtils.h" +#include "ClangTidyTest.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { + +namespace { +using namespace clang::ast_matchers; +using namespace utils::fixit; + +template +class ConstTransform : public ClangTidyCheck { +public: + ConstTransform(StringRef CheckName, ClangTidyContext *Context) + : ClangTidyCheck(CheckName, Context) {} + + void registerMatchers(MatchFinder *Finder) override { + Finder->addMatcher(varDecl(hasName("target")).bind("var"), this); + } + + void check(const MatchFinder::MatchResult &Result) override { + const auto *D = Result.Nodes.getNodeAs("var"); + using utils::fixit::addQualifierToVarDecl; + Optional Fix = addQualifierToVarDecl(*D, DeclSpec::TQ::TQ_const, + CT, CP, Result.Context); + auto Diag = diag(D->getBeginLoc(), "doing const transformation"); + if (Fix) + Diag << *Fix; + } +}; +} // namespace + +namespace test { +using PointeeLTransform = + ConstTransform; +using PointeeRTransform = + ConstTransform; + +using ValueLTransform = + ConstTransform; +using ValueRTransform = + ConstTransform; + +// ---------------------------------------------------------------------------- +// Test Value-like types. Everything with indirection is done later. +// ---------------------------------------------------------------------------- + +TEST(Values, Builtin) { + StringRef Snippet = "int target = 0;"; + + EXPECT_EQ("const int target = 0;", runCheckOnCode(Snippet)); + EXPECT_EQ("const int target = 0;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const target = 0;", runCheckOnCode(Snippet)); + EXPECT_EQ("int const target = 0;", + runCheckOnCode(Snippet)); +} +TEST(Values, TypedefBuiltin) { + StringRef T = "typedef int MyInt;"; + StringRef S = "MyInt target = 0;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, TypedefBuiltinPointer) { + StringRef T = "typedef int* MyInt;"; + StringRef S = "MyInt target = nullptr;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, UsingBuiltin) { + StringRef T = "using MyInt = int;"; + StringRef S = "MyInt target = 0;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = 0;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = 0;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, UsingBuiltinPointer) { + StringRef T = "using MyInt = int*;"; + StringRef S = "MyInt target = nullptr;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const MyInt target = nullptr;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("MyInt const target = nullptr;"), + runCheckOnCode(Cat(S))); +} +TEST(Values, AutoValue) { + StringRef T = "int f() { return 42; }\n"; + StringRef S = "auto target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, AutoPointer) { + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "auto target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, AutoReference) { + StringRef T = "static int global = 42; int& f() { return global; }\n"; + StringRef S = "auto target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const auto target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, DeclTypeValue) { + StringRef T = "int f() { return 42; }\n"; + StringRef S = "decltype(f()) target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, DeclTypePointer) { + // The pointer itself will be changed to 'const'. There is no + // way to make the pointee 'const' with this syntax. + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "decltype(f()) target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, DeclTypeReference) { + // Same as pointer, but the reference itself will be marked 'const'. + // This has no effect and will result in a warning afterwards. The + // transformation itself is still correct. + StringRef T = "static int global = 42; int& f() { return global; }\n"; + StringRef S = "decltype(f()) target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const decltype(f()) target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("decltype(f()) const target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Values, Parens) { + StringRef Snippet = "int ((target)) = 0;"; + + EXPECT_EQ("const int ((target)) = 0;", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int ((target)) = 0;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const ((target)) = 0;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const ((target)) = 0;", + runCheckOnCode(Snippet)); +} + +// ---------------------------------------------------------------------------- +// Test builtin-arrays +// ---------------------------------------------------------------------------- + +TEST(Arrays, Builtin) { + StringRef Snippet = "int target[][1] = {{1}, {2}, {3}};"; + + EXPECT_EQ("const int target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const target[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, BuiltinParens) { + StringRef Snippet = "int ((target))[][1] = {{1}, {2}, {3}};"; + + EXPECT_EQ("const int ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const ((target))[][1] = {{1}, {2}, {3}};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, Pointers) { + StringRef Snippet = "int x; int* target[] = {&x, &x, &x};"; + + EXPECT_EQ("int x; const int* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int const* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x; int* const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int* const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, PointerPointers) { + StringRef Snippet = "int* x = nullptr; int** target[] = {&x, &x, &x};"; + + EXPECT_EQ("int* x = nullptr; int* const* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* x = nullptr; int** const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int* x = nullptr; int* const* target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* x = nullptr; int** const target[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); +} +TEST(Arrays, PointersParens) { + StringRef Snippet = "int x; int* (target)[] = {&x, &x, &x};"; + + EXPECT_EQ("int x; const int* (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int const* (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x; int* const (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x; int* const (target)[] = {&x, &x, &x};", + runCheckOnCode(Snippet)); +} + +// ---------------------------------------------------------------------------- +// Test reference types. This does not include pointers and arrays. +// ---------------------------------------------------------------------------- + +TEST(Reference, LValueBuiltin) { + StringRef Snippet = "int x = 42; int& target = x;"; + + EXPECT_EQ("int x = 42; const int& target = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; const int& target = x;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x = 42; int const& target = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; int const& target = x;", + runCheckOnCode(Snippet)); +} +TEST(Reference, RValueBuiltin) { + StringRef Snippet = "int&& target = 42;"; + EXPECT_EQ("const int&& target = 42;", + runCheckOnCode(Snippet)); + EXPECT_EQ("const int&& target = 42;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int const&& target = 42;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const&& target = 42;", + runCheckOnCode(Snippet)); +} +TEST(Reference, LValueToPointer) { + StringRef Snippet = "int* p; int *& target = p;"; + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* p; int * const& target = p;", + runCheckOnCode(Snippet)); +} +TEST(Reference, LValueParens) { + StringRef Snippet = "int x = 42; int ((& target)) = x;"; + + EXPECT_EQ("int x = 42; const int ((& target)) = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; const int ((& target)) = x;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int x = 42; int const((& target)) = x;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int x = 42; int const((& target)) = x;", + runCheckOnCode(Snippet)); +} +TEST(Reference, ToArray) { + StringRef ArraySnippet = "int a[4] = {1, 2, 3, 4};"; + StringRef Snippet = "int (&target)[4] = a;"; + auto Cat = [&ArraySnippet](StringRef S) { return (ArraySnippet + S).str(); }; + + EXPECT_EQ(Cat("const int (&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("const int (&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); + + EXPECT_EQ(Cat("int const(&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("int const(&target)[4] = a;"), + runCheckOnCode(Cat(Snippet))); +} +TEST(Reference, Auto) { + StringRef T = "static int global = 42; int& f() { return global; }\n"; + StringRef S = "auto& target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const auto& target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const& target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("const auto& target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const& target = f();"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test pointers types. +// ---------------------------------------------------------------------------- + +TEST(Pointers, SingleBuiltin) { + StringRef Snippet = "int* target = nullptr;"; + + EXPECT_EQ("int* const target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* const target = nullptr;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("const int* target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int const* target = nullptr;", + runCheckOnCode(Snippet)); +} +TEST(Pointers, MultiBuiltin) { + StringRef Snippet = "int** target = nullptr;"; + + EXPECT_EQ("int** const target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int** const target = nullptr;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int* const* target = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int* const* target = nullptr;", + runCheckOnCode(Snippet)); +} +TEST(Pointers, ToArray) { + StringRef ArraySnippet = "int a[4] = {1, 2, 3, 4};"; + StringRef Snippet = "int (*target)[4] = &a;"; + auto Cat = [&ArraySnippet](StringRef S) { return (ArraySnippet + S).str(); }; + + EXPECT_EQ(Cat("int (*const target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("const int (*target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); + + EXPECT_EQ(Cat("int (*const target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); + EXPECT_EQ(Cat("int const(*target)[4] = &a;"), + runCheckOnCode(Cat(Snippet))); +} +TEST(Pointers, Parens) { + StringRef Snippet = "int ((**target)) = nullptr;"; + + EXPECT_EQ("int ((**const target)) = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int ((**const target)) = nullptr;", + runCheckOnCode(Snippet)); + + EXPECT_EQ("int ((* const*target)) = nullptr;", + runCheckOnCode(Snippet)); + EXPECT_EQ("int ((* const*target)) = nullptr;", + runCheckOnCode(Snippet)); +} +TEST(Pointers, Auto) { + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "auto* target = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("auto* const target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto* const target = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("const auto* target = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const* target = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Pointers, AutoParens) { + StringRef T = "int* f() { return nullptr; }\n"; + StringRef S = "auto (((* target))) = f();"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("auto (((* const target))) = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto (((* const target))) = f();"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("const auto (((* target))) = f();"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("auto const(((* target))) = f();"), + runCheckOnCode(Cat(S))); +} +TEST(Pointers, FunctionPointer) { + StringRef S = "int (*target)(float, int, double) = nullptr;"; + + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + EXPECT_EQ("int (*const target)(float, int, double) = nullptr;", + runCheckOnCode(S)); + + S = "int (((*target)))(float, int, double) = nullptr;"; + EXPECT_EQ("int (((*const target)))(float, int, double) = nullptr;", + runCheckOnCode(S)); +} +TEST(Pointers, MemberFunctionPointer) { + StringRef T = "struct A { int f() { return 1; } };"; + StringRef S = "int (A::*target)() = &A::f;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int (A::*const target)() = &A::f;"), + runCheckOnCode(Cat(S))); + + S = "int (A::*((target)))() = &A::f;"; + EXPECT_EQ(Cat("int (A::*const ((target)))() = &A::f;"), + runCheckOnCode(Cat(S))); +} +TEST(Pointers, MemberDataPointer) { + StringRef T = "struct A { int member = 0; };"; + StringRef S = "int A::*target = &A::member;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("int A::*const target = &A::member;"), + runCheckOnCode(Cat(S))); + + S = "int A::*((target)) = &A::member;"; + EXPECT_EQ(Cat("int A::*const ((target)) = &A::member;"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test TagTypes (struct, class, unions, enums) +// ---------------------------------------------------------------------------- + +TEST(TagTypes, Struct) { + StringRef T = "struct Foo { int data; int method(); };\n"; + StringRef S = "struct Foo target{0};"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const struct Foo target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const struct Foo target{0};"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("struct Foo const target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("struct Foo const target{0};"), + runCheckOnCode(Cat(S))); + + S = "Foo target{0};"; + EXPECT_EQ(Cat("const Foo target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target{0};"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target{0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target{0};"), + runCheckOnCode(Cat(S))); + + S = "Foo (target){0};"; + EXPECT_EQ(Cat("const Foo (target){0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target){0};"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target){0};"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target){0};"), + runCheckOnCode(Cat(S))); +} +TEST(TagTypes, Class) { + StringRef T = "class Foo { int data; int method(); };\n"; + StringRef S = "class Foo target;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const class Foo target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const class Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("class Foo const target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("class Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo target;"; + EXPECT_EQ(Cat("const Foo target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo (target);"; + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); +} +TEST(TagTypes, Enum) { + StringRef T = "enum Foo { N_ONE, N_TWO, N_THREE };\n"; + StringRef S = "enum Foo target;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const enum Foo target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const enum Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("enum Foo const target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("enum Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo target;"; + EXPECT_EQ(Cat("const Foo target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo (target);"; + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); +} +TEST(TagTypes, Union) { + StringRef T = "union Foo { int yay; float nej; };\n"; + StringRef S = "union Foo target;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("const union Foo target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const union Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("union Foo const target;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("union Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo target;"; + EXPECT_EQ(Cat("const Foo target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo target;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const target;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const target;"), + runCheckOnCode(Cat(S))); + + S = "Foo (target);"; + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("const Foo (target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("Foo const (target);"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test Macro expansions. +// ---------------------------------------------------------------------------- + +TEST(Macro, AllInMacro) { + StringRef T = "#define DEFINE_VARIABLE int target = 42\n"; + StringRef S = "DEFINE_VARIABLE;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE;"), runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroParameter) { + StringRef T = "#define DEFINE_VARIABLE(X) int X = 42\n"; + StringRef S = "DEFINE_VARIABLE(target);"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("DEFINE_VARIABLE(target);"), + runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroTypeValue) { + StringRef T = "#define BAD_TYPEDEF int\n"; + StringRef S = "BAD_TYPEDEF target = 42;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("BAD_TYPEDEF target = 42;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF target = 42;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("BAD_TYPEDEF const target = 42;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF const target = 42;"), + runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroTypePointer) { + StringRef T = "#define BAD_TYPEDEF int *\n"; + StringRef S = "BAD_TYPEDEF target = nullptr;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("BAD_TYPEDEF const target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF const target = nullptr;"), + runCheckOnCode(Cat(S))); + + // FIXME: Failing even all parts seem to bail-out in for isMacroID() + EXPECT_NE(Cat("BAD_TYPEDEF target = nullptr;"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("BAD_TYPEDEF target = nullptr;"), + runCheckOnCode(Cat(S))); +} +TEST(Macro, MacroTypeReference) { + StringRef T = "static int g = 42;\n#define BAD_TYPEDEF int&\n"; + StringRef S = "BAD_TYPEDEF target = g;"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); + // FIXME: Failing even all parts seem to bail-out in for isMacroID() + EXPECT_NE(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); + // FIXME: Failing even all parts seem to bail-out in for isMacroID() + EXPECT_NE(Cat("BAD_TYPEDEF target = g;"), + runCheckOnCode(Cat(S))); +} + +// ---------------------------------------------------------------------------- +// Test template code. +// ---------------------------------------------------------------------------- + +TEST(Template, TemplateVariable) { + StringRef T = "template T target = 3.1415;"; + + EXPECT_EQ("template const T target = 3.1415;", + runCheckOnCode(T)); + EXPECT_EQ("template T const target = 3.1415;", + runCheckOnCode(T)); + + EXPECT_EQ("template const T target = 3.1415;", + runCheckOnCode(T)); + EXPECT_EQ("template T const target = 3.1415;", + runCheckOnCode(T)); +} +TEST(Template, FunctionValue) { + StringRef T = "template void f(T v) \n"; + StringRef S = "{ T target = v; }"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, FunctionPointer) { + StringRef T = "template void f(T* v) \n"; + StringRef S = "{ T* target = v; }"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T* target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const* target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, FunctionReference) { + StringRef T = "template void f(T& v) \n"; + StringRef S = "{ T& target = v; }"; + auto Cat = [&T](StringRef S) { return (T + S).str(); }; + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, MultiInstantiationsFunction) { + StringRef T = "template void f(T v) \n"; + StringRef S = "{ T target = v; }"; + StringRef InstantStart = "void calls() {\n"; + StringRef InstValue = "f(42);\n"; + StringRef InstConstValue = "f(42);\n"; + StringRef InstPointer = "f(nullptr);\n"; + StringRef InstPointerConst = "f(nullptr);\n"; + StringRef InstConstPointer = "f(nullptr);\n"; + StringRef InstConstPointerConst = "f(nullptr);\n"; + StringRef InstRef = "int i = 42;\nf(i);\n"; + StringRef InstConstRef = "f(i);\n"; + StringRef InstantEnd = "}"; + auto Cat = [&](StringRef Target) { + return (T + Target + InstantStart + InstValue + InstConstValue + + InstPointer + InstPointerConst + InstConstPointer + + InstConstPointerConst + InstRef + InstConstRef + InstantEnd) + .str(); + }; + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); +} + +TEST(Template, StructValue) { + StringRef T = "template struct S { void f(T& v) \n"; + StringRef S = "{ T target = v; }"; + StringRef End = "\n};"; + auto Cat = [&T, &End](StringRef S) { return (T + S + End).str(); }; + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, StructPointer) { + StringRef T = "template struct S { void f(T* v) \n"; + StringRef S = "{ T* target = v; }"; + StringRef End = "\n};"; + auto Cat = [&T, &End](StringRef S) { return (T + S + End).str(); }; + + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T* const target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T* target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const* target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, StructReference) { + StringRef T = "template struct S { void f(T& v) \n"; + StringRef S = "{ T& target = v; }"; + StringRef End = "\n};"; + auto Cat = [&T, &End](StringRef S) { return (T + S + End).str(); }; + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const T& target = v; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ T const& target = v; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, DependentReturnFunction) { + StringRef TS = "template struct TS { using value_type = T; };"; + StringRef T = "template void foo() "; + StringRef S = "{ typename T::value_type target; }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ const typename T::value_type target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const target; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const typename T::value_type target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const target; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, DependentReturnPointerFunction) { + StringRef TS = "template struct TS { using value_type = T; };"; + StringRef T = "template void foo() "; + StringRef S = "{ typename T::value_type *target; }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ typename T::value_type *const target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type *const target; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const typename T::value_type *target; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const*target; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, DependentReturnReferenceFunction) { + StringRef TS = "template struct TS { using value_type = T; };"; + StringRef T = "template void foo(T& f) "; + StringRef S = "{ typename T::value_type &target = f; }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ const typename T::value_type &target = f; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const&target = f; }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const typename T::value_type &target = f; }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ typename T::value_type const&target = f; }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, VectorLikeType) { + StringRef TS = "template struct TS { TS(const T&) {} }; "; + StringRef T = "void foo() "; + StringRef S = "{ TS target(42); }"; + auto Cat = [&TS, &T](StringRef S) { return (TS + T + S).str(); }; + + EXPECT_EQ(Cat("{ const TS target(42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42); }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const TS target(42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42); }"), + runCheckOnCode(Cat(S))); +} +TEST(Template, SpecializedTemplate) { + StringRef TS = "template struct TS { TS(const T&) {} }; "; + StringRef TS2 = "template <> struct TS { TS(const double&) {} }; "; + StringRef T = "void foo() "; + StringRef S = "{ TS target(42.42); }"; + auto Cat = [&](StringRef S) { return (TS + TS2 + T + S).str(); }; + + EXPECT_EQ(Cat("{ const TS target(42.42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42.42); }"), + runCheckOnCode(Cat(S))); + + EXPECT_EQ(Cat("{ const TS target(42.42); }"), + runCheckOnCode(Cat(S))); + EXPECT_EQ(Cat("{ TS const target(42.42); }"), + runCheckOnCode(Cat(S))); +} + +} // namespace test +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt --- a/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt +++ b/clang-tools-extra/unittests/clang-tidy/CMakeLists.txt @@ -7,6 +7,7 @@ include_directories(${CLANG_LINT_SOURCE_DIR}) add_extra_unittest(ClangTidyTests + AddConstTest.cpp ClangTidyDiagnosticConsumerTest.cpp ClangTidyOptionsTest.cpp IncludeInserterTest.cpp