Index: clang-tidy/cppcoreguidelines/CMakeLists.txt =================================================================== --- clang-tidy/cppcoreguidelines/CMakeLists.txt +++ clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule AvoidGotoCheck.cpp + ConstCheck.cpp CppCoreGuidelinesTidyModule.cpp InterfacesGlobalInitCheck.cpp NoMallocCheck.cpp Index: clang-tidy/cppcoreguidelines/ConstCheck.h =================================================================== --- /dev/null +++ clang-tidy/cppcoreguidelines/ConstCheck.h @@ -0,0 +1,57 @@ +//===--- ConstCheck.h - clang-tidy-------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// 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_CONSTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCHECK_H + +#include "../ClangTidy.h" +#include "../utils/ExprMutationAnalyzer.h" + +namespace clang { +namespace tidy { + +namespace utils { +class ExprMutationAnalyzer; +} + +namespace cppcoreguidelines { + +/// This check warns for every variable, that could be declared as const, but +/// isn't. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-const.html +class ConstCheck : public ClangTidyCheck { +public: + ConstCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AnalyzeValues(Options.get("AnalyzeValues", 1)), + AnalyzeReferences(Options.get("AnalyzeReferences", 1)), + WarnPointersAsValues(Options.get("WarnPointersAsValues", 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 *Scope, ASTContext *Context); + + using MutationAnalyzer = std::unique_ptr; + llvm::DenseMap Scopes; + + const bool AnalyzeValues; + const bool AnalyzeReferences; + const bool WarnPointersAsValues; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_CONSTCHECK_H Index: clang-tidy/cppcoreguidelines/ConstCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/cppcoreguidelines/ConstCheck.cpp @@ -0,0 +1,221 @@ +//===--- ConstCheck.cpp - clang-tidy---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ConstCheck.h" +// #include "../utils/ASTUtils.h" +// #include "../utils/DeclRefExprUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/* + * General Thoughts + * ================ + * + * For now: Only local variables are considered. Globals/namespace variables, + * paramters and class members are not analyzed. + * Parameters have a check already: readability-non-const-parameter + * + * + * Handle = either a pointer or reference + * Value = everything else (Type variable_name;) + * + * Value Semantic + * ============== + * - it is neither global nor namespace level + CHECK + * - it never gets assigned to after initialization + CHECK + * -> every uninitialized variable can not be const + CHECK + * - no non-const handle is created with it + CHECK + * - no non-const pointer from it + CHECK + * - no non-const pointer argument + CHECK + * - no non-const reference from it + CHECK + * - no non-const reference argument + CHECK + * - no non-const capture by reference in a lambda + CHECK + * - it is not returned as non-const handle from a function + CHECK + * - it address is not assigned to an out pointer parameter + CHECK + * + * primitive Builtins + * ---------------- + * - it is not modified with an operator (++i,i++,--i,i--) + CHECK + * - it is not modified with an operator-assignment + CHECK + * + * objects + * ------- + * - there is no non-const access to a member + * - there is no call to a non-const method + CHECK + * - there is no call to an non-const overloaded operator + CHECK + * - there is no non-const iterator created from this type + CHECK + * (std::begin and friends) + * + * arrays + * ------ + * - there is no non-const operator[] access + CHECK + * - there is no non-const handle creation of one of the elements + CHECK + * - there is no non-const iterator created from this type + CHECK + * (std::begin and friends) + * + * templated variables + * ------------------- + * - one can not reason about templated variables, because every sensible + * operation is overloadable and different instantiations will result + * in types with different const-properties. + * - Example: operator+(T& lhs, T& rhs) -> modification might occur for this + * type + * -> this fordbids `val = val1 + val2` val1 and val2 to be const + * - only concepts give possibility to infer constness of templated variables + * + * Handle Semantic + * =============== + * - modification of the pointee prohibit constness + * - Handles follow the typ of the pointee + * + * - no assignment to the target of the handle + * + * pointers + * -------- + * - match both for value and handle semantic + * + * references + * ---------- + * - only handle semantic applies + * - references to templated types suffer from the same problems as templated + * values + * + * forwarding reference + * -------------------- + * - same as references? + * + * Implementation strategy + * ======================= + * + * - Register every declared local variable/constant with value semantic. + * (pointers, values) + * Store if they can be made const. + * (const int i = 10 : no, + * int *const = &i : no, + * int i = 10 : yes, -> const int i = 10 + * int *p_i = &i : yes, -> int *const p_i = &i) + * - Register every declared local variable/constant with handle semantic. + * (pointers, references) + * Store if they can be made const, meaning if they can be a const target + * (const int *cp_i = &i : no, + * const int &cr_i = i : no, + * int *p_i = &i : yes, -> const int *p_i = &i + * int &r_i = i : yes, -> const int &r_i = i) + * - Keep 2 dictionaries for values and handles + * + * - Match operations/events that forbid values to be const -> mark then 'no' + * - Match operations/events that forbid handles to be const -> mark then 'no' + * + * - once the translation unit is finished, determine what can be const, by + * just iterating over all keys and check if they map to 'true'. + * - values that can be const -> emit warning for their type and name + * - handles that can be const -> emit warning for the pointee type and name + * - ignore the rest + * + */ + +namespace { +AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); } +} // namespace + +void ConstCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AnalyzeValues", AnalyzeValues); + Options.store(Opts, "AnalyzeReferences", AnalyzeReferences); + Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues); +} + +void ConstCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + const auto ConstType = hasType(isConstQualified()); + const auto ConstReference = hasType(references(isConstQualified())); + const auto TemplateType = anyOf(hasType(templateTypeParmType()), + hasType(substTemplateTypeParmType())); + + // Match local variables, that could be const. + // Example: `int i = 10`, `int i` (will be used if program is correct) + const auto LocalValDecl = varDecl(allOf( + isLocal(), hasInitializer(anything()), unless(ConstType), + unless(ConstReference), unless(TemplateType), unless(isImplicit()))); + + // Match the function scope for which the analysis of all local variables + // shall be run. + const auto FunctionScope = + functionDecl(allOf(hasBody(compoundStmt().bind("scope")), + findAll(LocalValDecl.bind("new-local-value")))); + Finder->addMatcher(FunctionScope, this); +} + +void ConstCheck::check(const MatchFinder::MatchResult &Result) { + if (!getLangOpts().CPlusPlus) + return; + + const auto *Scope = Result.Nodes.getNodeAs("scope"); + registerScope(Scope, Result.Context); + + const auto *Variable = Result.Nodes.getNodeAs("new-local-value"); + assert(Variable && "Did not match local variable definition"); + + if (Variable->getType()->isPointerType() && !WarnPointersAsValues) + return; + + if (Variable->getType()->isReferenceType() && !AnalyzeReferences) + return; + + if (!AnalyzeValues || Scopes[Scope]->isMutated(Variable)) + return; + + diag(Variable->getLocStart(), "variable %0 of type %1 can be declared const") + << Variable << Variable->getType(); +#if 0 + if (const auto *Variable = + Result.Nodes.getNodeAs("new-local-handle")) { + assert(AnalyzeHandles && "Matched local handle without analyzing them"); + + if (usedNonConst(Variable, Scope, Result)) + return; + + // if (!utils::decl_ref_expr::isOnlyUsedAsConst(*Variable, *Scope, + // *Result.Context)) + // return; + + // Differentiate between pointers and references. + QualType HandleType = Variable->getType(); + if (HandleType->isReferenceType()) + diag(Variable->getLocStart(), + "reference variable %0 of type %1 can be declared const") + << Variable << HandleType; + else if (HandleType->isPointerType()) + diag(Variable->getLocStart(), + "pointer variable %0 of type %1 can be declared const") + << Variable << HandleType; + else + llvm_unreachable("Expected handle type"); + } +#endif +} + +void ConstCheck::registerScope(const CompoundStmt *Scope, ASTContext *Context) { + if (Scopes.find(Scope) == Scopes.end()) { + Scopes.insert(std::make_pair( + Scope, llvm::make_unique(Scope, Context))); + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang Index: clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp =================================================================== --- clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -12,6 +12,7 @@ #include "../ClangTidyModuleRegistry.h" #include "../misc/UnconventionalAssignOperatorCheck.h" #include "AvoidGotoCheck.h" +#include "ConstCheck.h" #include "InterfacesGlobalInitCheck.h" #include "NoMallocCheck.h" #include "OwningMemoryCheck.h" @@ -38,6 +39,8 @@ void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { CheckFactories.registerCheck( "cppcoreguidelines-avoid-goto"); + CheckFactories.registerCheck( + "cppcoreguidelines-const"); CheckFactories.registerCheck( "cppcoreguidelines-interfaces-global-init"); CheckFactories.registerCheck("cppcoreguidelines-no-malloc"); Index: clang-tidy/utils/CMakeLists.txt =================================================================== --- clang-tidy/utils/CMakeLists.txt +++ clang-tidy/utils/CMakeLists.txt @@ -3,6 +3,7 @@ add_clang_library(clangTidyUtils ASTUtils.cpp DeclRefExprUtils.cpp + ExprMutationAnalyzer.cpp ExprSequence.cpp FixItHintUtils.cpp HeaderFileExtensionsUtils.cpp Index: clang-tidy/utils/ExprMutationAnalyzer.h =================================================================== --- /dev/null +++ clang-tidy/utils/ExprMutationAnalyzer.h @@ -0,0 +1,54 @@ +//===---------- ExprMutationAnalyzer.h - clang-tidy -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H + +#include + +#include "clang/AST/AST.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +namespace tidy { +namespace utils { + +/// Analyzes whether any mutative operations are applied to an expression within +/// a given statement. +class ExprMutationAnalyzer { +public: + ExprMutationAnalyzer(const Stmt *Stm, ASTContext *Context) + : Stm(Stm), Context(Context) {} + + bool isMutated(const Decl *Dec) { return findDeclMutation(Dec) != nullptr; } + bool isMutated(const Expr *Exp) { return findMutation(Exp) != nullptr; } + const Stmt *findMutation(const Expr *Exp); + +private: + const Stmt *findExprMutation(ArrayRef Matches); + const Stmt *findDeclMutation(ArrayRef Matches); + const Stmt *findDeclMutation(const Decl *Dec); + + const Stmt *findDirectMutation(const Expr *Exp); + const Stmt *findMemberMutation(const Expr *Exp); + const Stmt *findArrayElementMutation(const Expr *Exp); + const Stmt *findCastMutation(const Expr *Exp); + const Stmt *findRangeLoopMutation(const Expr *Exp); + const Stmt *findReferenceMutation(const Expr *Exp); + + const Stmt *const Stm; + ASTContext *const Context; + llvm::DenseMap Results; +}; + +} // namespace utils +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_EXPRMUTATIONANALYZER_H Index: clang-tidy/utils/ExprMutationAnalyzer.cpp =================================================================== --- /dev/null +++ clang-tidy/utils/ExprMutationAnalyzer.cpp @@ -0,0 +1,212 @@ +//===---------- ExprMutationAnalyzer.cpp - clang-tidy ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "ExprMutationAnalyzer.h" + +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace utils { +using namespace ast_matchers; + +namespace { + +AST_MATCHER_P(LambdaExpr, hasCaptureInit, const Expr *, E) { + for (const auto *Init : Node.capture_inits()) { + if (Init == E) + return true; + } + return false; +} + +AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt, + ast_matchers::internal::Matcher, InnerMatcher) { + const DeclStmt *const Range = Node.getRangeStmt(); + return InnerMatcher.matches(*Range, Finder, Builder); +} + +const auto nonConstReferenceType = [] { + return referenceType(pointee(unless(isConstQualified()))); +}; + +} // namespace + +const Stmt *ExprMutationAnalyzer::findMutation(const Expr *Exp) { + const auto Memoized = Results.find(Exp); + if (Memoized != Results.end()) { + return Memoized->second; + } + + for (const auto &Finder : {&ExprMutationAnalyzer::findDirectMutation, + &ExprMutationAnalyzer::findMemberMutation, + &ExprMutationAnalyzer::findArrayElementMutation, + &ExprMutationAnalyzer::findCastMutation, + &ExprMutationAnalyzer::findRangeLoopMutation, + &ExprMutationAnalyzer::findReferenceMutation}) { + if (const Stmt *S = (this->*Finder)(Exp)) + return Results[Exp] = S; + } + + return Results[Exp] = nullptr; +} + +const Stmt * +ExprMutationAnalyzer::findExprMutation(ArrayRef Matches) { + for (const auto &Nodes : Matches) { + if (const Stmt *S = findMutation(Nodes.getNodeAs("expr"))) + return S; + } + return nullptr; +} + +const Stmt * +ExprMutationAnalyzer::findDeclMutation(ArrayRef Matches) { + for (const auto &DeclNodes : Matches) { + if (const auto *S = findDeclMutation(DeclNodes.getNodeAs("decl"))) + return S; + } + return nullptr; +} + +const Stmt *ExprMutationAnalyzer::findDeclMutation(const Decl *Dec) { + const auto Refs = match( + findAll(declRefExpr(to(equalsNode(Dec))).bind("expr")), *Stm, *Context); + for (const auto &RefNodes : Refs) { + const auto *E = RefNodes.getNodeAs("expr"); + if (findMutation(E)) + return E; + } + return nullptr; +} + +const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) { + // LHS of any assignment operators. + const auto AsAssignmentLhs = + binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp))); + + // Operand of increment/decrement operators. + const auto AsIncDecOperand = + unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), + hasUnaryOperand(equalsNode(Exp))); + + // Invoking non-const member function. + const auto NonConstMethod = cxxMethodDecl(unless(isConst())); + const auto AsNonConstThis = + expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))), + cxxOperatorCallExpr(callee(NonConstMethod), + hasArgument(0, equalsNode(Exp))))); + + // Taking address of 'Exp'. + // We're assuming 'Exp' is mutated as soon as its address is taken, though in + // theory we can follow the pointer and see whether it escaped `Stm` or is + // dereferenced and then mutated. This is left for future improvements. + const auto AsAmpersandOperand = + unaryOperator(hasOperatorName("&"), + // A NoOp implicit cast is adding const. + unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), + hasUnaryOperand(equalsNode(Exp))); + const auto AsPointerFromArrayDecay = + castExpr(hasCastKind(CK_ArrayToPointerDecay), + unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp))); + + // Used as non-const-ref argument when calling a function. + const auto NonConstRefParam = forEachArgumentWithParam( + equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType()))); + const auto AsNonConstRefArg = + anyOf(callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam)); + + // Captured by a lambda by reference. + // If we're initializing a capture with 'Exp' directly then we're initializing + // a reference capture. + // For value captures there will be an ImplicitCastExpr . + const auto AsLambdaRefCaptureInit = lambdaExpr(hasCaptureInit(Exp)); + + // Returned as non-const-ref. + // If we're returning 'Exp' directly then it's returned as non-const-ref. + // For returning by value there will be an ImplicitCastExpr . + // For returning by const-ref there will be an ImplicitCastExpr (for + // adding const.) + const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp))); + + const auto Matches = + match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis, + AsAmpersandOperand, AsPointerFromArrayDecay, + AsNonConstRefArg, AsLambdaRefCaptureInit, + AsNonConstRefReturn)) + .bind("stmt")), + *Stm, *Context); + return selectFirst("stmt", Matches); +} + +const Stmt *ExprMutationAnalyzer::findMemberMutation(const Expr *Exp) { + // Check whether any member of 'Exp' is mutated. + const auto MemberExprs = match( + findAll(memberExpr(hasObjectExpression(equalsNode(Exp))).bind("expr")), + *Stm, *Context); + return findExprMutation(MemberExprs); +} + +const Stmt *ExprMutationAnalyzer::findArrayElementMutation(const Expr *Exp) { + // Check whether any element of an array is mutated. + const auto SubscriptExprs = match( + findAll(arraySubscriptExpr(hasBase(ignoringImpCasts(equalsNode(Exp)))) + .bind("expr")), + *Stm, *Context); + return findExprMutation(SubscriptExprs); +} + +const Stmt *ExprMutationAnalyzer::findCastMutation(const Expr *Exp) { + // If 'Exp' is casted to any non-const reference type, check the castExpr. + const auto Casts = + match(findAll(castExpr(hasSourceExpression(equalsNode(Exp)), + anyOf(explicitCastExpr(hasDestinationType( + nonConstReferenceType())), + implicitCastExpr(hasImplicitDestinationType( + nonConstReferenceType())))) + .bind("expr")), + *Stm, *Context); + return findExprMutation(Casts); +} + +const Stmt *ExprMutationAnalyzer::findRangeLoopMutation(const Expr *Exp) { + // If range for looping over 'Exp' with a non-const reference loop variable, + // check all declRefExpr of the loop variable. + const auto LoopVars = + match(findAll(cxxForRangeStmt( + hasLoopVariable( + varDecl(hasType(nonConstReferenceType())).bind("decl")), + hasRangeInit(equalsNode(Exp)))), + *Stm, *Context); + return findDeclMutation(LoopVars); +} + +const Stmt *ExprMutationAnalyzer::findReferenceMutation(const Expr *Exp) { + // If 'Exp' is bound to a non-const reference, check all declRefExpr to that. + const auto Refs = match( + stmt(forEachDescendant( + varDecl( + hasType(nonConstReferenceType()), + hasInitializer(anyOf(equalsNode(Exp), + conditionalOperator(anyOf( + hasTrueExpression(equalsNode(Exp)), + hasFalseExpression(equalsNode(Exp)))))), + hasParent(declStmt().bind("stmt")), + // Don't follow the reference in range statement, we've handled + // that separately. + unless(hasParent(declStmt(hasParent( + cxxForRangeStmt(hasRangeStmt(equalsBoundNode("stmt")))))))) + .bind("decl"))), + *Stm, *Context); + return findDeclMutation(Refs); +} + +} // namespace utils +} // namespace tidy +} // namespace clang + Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -57,6 +57,11 @@ Improvements to clang-tidy -------------------------- +- New :doc:`cppcoreguidelines-const + ` check + + FIXME: add release notes. + - New module `abseil` for checks related to the `Abseil `_ library. Index: docs/clang-tidy/checks/cppcoreguidelines-const.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/cppcoreguidelines-const.rst @@ -0,0 +1,6 @@ +.. title:: clang-tidy - cppcoreguidelines-const + +cppcoreguidelines-const +======================= + +FIXME: Describe what patterns does the check detect and why. Give examples. Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -76,6 +76,7 @@ cert-oop11-cpp (redirects to performance-move-constructor-init) cppcoreguidelines-avoid-goto cppcoreguidelines-c-copy-assignment-signature (redirects to misc-unconventional-assign-operator) + cppcoreguidelines-const cppcoreguidelines-interfaces-global-init cppcoreguidelines-no-malloc cppcoreguidelines-owning-memory Index: test/clang-tidy/cppcoreguidelines-const-pointers-as-values.cpp =================================================================== --- /dev/null +++ test/clang-tidy/cppcoreguidelines-const-pointers-as-values.cpp @@ -0,0 +1,11 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-const %t \ +// RUN: -config='{CheckOptions: \ +// RUN: [{key: "cppcoreguidelines-const.AnalyzeValues", value: 1},\ +// RUN: {key: "cppcoreguidelines-const.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 +} Index: test/clang-tidy/cppcoreguidelines-const-values.cpp =================================================================== --- /dev/null +++ test/clang-tidy/cppcoreguidelines-const-values.cpp @@ -0,0 +1,557 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-const %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 + +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(); +} Index: unittests/clang-tidy/CMakeLists.txt =================================================================== --- unittests/clang-tidy/CMakeLists.txt +++ unittests/clang-tidy/CMakeLists.txt @@ -9,6 +9,7 @@ add_extra_unittest(ClangTidyTests ClangTidyDiagnosticConsumerTest.cpp ClangTidyOptionsTest.cpp + ExprMutationAnalyzerTest.cpp IncludeInserterTest.cpp GoogleModuleTest.cpp LLVMModuleTest.cpp Index: unittests/clang-tidy/ExprMutationAnalyzerTest.cpp =================================================================== --- /dev/null +++ unittests/clang-tidy/ExprMutationAnalyzerTest.cpp @@ -0,0 +1,554 @@ +//===---------- ExprMutationAnalyzerTest.cpp - clang-tidy -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../clang-tidy/utils/ExprMutationAnalyzer.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/Tooling.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace tidy { +namespace test { + +using namespace clang::ast_matchers; +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::ResultOf; +using ::testing::StartsWith; +using ::testing::Values; + +namespace { + +using ExprMatcher = internal::Matcher; +using StmtMatcher = internal::Matcher; + +ExprMatcher declRefTo(StringRef Name) { + return declRefExpr(to(namedDecl(hasName(Name)))); +} + +StmtMatcher withEnclosingCompound(ExprMatcher Matcher) { + return expr(Matcher, hasAncestor(compoundStmt().bind("stmt"))).bind("expr"); +} + +bool isMutated(const SmallVectorImpl &Results, ASTUnit *AST) { + const auto *const S = selectFirst("stmt", Results); + const auto *const E = selectFirst("expr", Results); + return utils::ExprMutationAnalyzer(S, &AST->getASTContext()).isMutated(E); +} + +SmallVector +mutatedBy(const SmallVectorImpl &Results, ASTUnit *AST) { + const auto *const S = selectFirst("stmt", Results); + SmallVector Chain; + utils::ExprMutationAnalyzer Analyzer(S, &AST->getASTContext()); + for (const auto *E = selectFirst("expr", Results); E != nullptr;) { + const Stmt *By = Analyzer.findMutation(E); + std::string buffer; + llvm::raw_string_ostream stream(buffer); + By->printPretty(stream, nullptr, AST->getASTContext().getPrintingPolicy()); + Chain.push_back(StringRef(stream.str()).trim().str()); + E = dyn_cast(By); + } + return Chain; +} + +std::string removeSpace(std::string s) { + s.erase(std::remove_if(s.begin(), s.end(), + [](char c) { return std::isspace(c); }), + s.end()); + return s; +} + +} // namespace + +TEST(ExprMutationAnalyzerTest, Trivial) { + const auto AST = tooling::buildASTFromCode("void f() { int x; x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +class AssignmentTest : public ::testing::TestWithParam {}; + +TEST_P(AssignmentTest, AssignmentModifies) { + const std::string ModExpr = "x " + GetParam() + " 10"; + const auto AST = + tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr)); +} + +INSTANTIATE_TEST_CASE_P(AllAssignmentOperators, AssignmentTest, + Values("=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", + "^=", "<<=", ">>="), ); + +class IncDecTest : public ::testing::TestWithParam {}; + +TEST_P(IncDecTest, IncDecModifies) { + const std::string ModExpr = GetParam(); + const auto AST = + tooling::buildASTFromCode("void f() { int x; " + ModExpr + "; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre(ModExpr)); +} + +INSTANTIATE_TEST_CASE_P(AllIncDecOperators, IncDecTest, + Values("++x", "--x", "x++", "x--"), ); + +TEST(ExprMutationAnalyzerTest, NonConstMemberFunc) { + const auto AST = tooling::buildASTFromCode( + "void f() { struct Foo { void mf(); }; Foo x; x.mf(); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.mf()")); +} + +TEST(ExprMutationAnalyzerTest, ConstMemberFunc) { + const auto AST = tooling::buildASTFromCode( + "void f() { struct Foo { void mf() const; }; Foo x; x.mf(); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, NonConstOperator) { + const auto AST = tooling::buildASTFromCode( + "void f() { struct Foo { Foo& operator=(int); }; Foo x; x = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x = 10")); +} + +TEST(ExprMutationAnalyzerTest, ConstOperator) { + const auto AST = tooling::buildASTFromCode( + "void f() { struct Foo { int operator()() const; }; Foo x; x(); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, ByValueArgument) { + auto AST = + tooling::buildASTFromCode("void g(int); void f() { int x; g(x); }"); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A {}; A operator+(A, int); A x; x + 1; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A { A(int); }; int x; A y(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A { A(); A(A); }; A x; A y(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, ByNonConstRefArgument) { + auto AST = + tooling::buildASTFromCode("void g(int&); void f() { int x; g(x); }"); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("g(x)")); + + AST = tooling::buildASTFromCode( + "void f() { struct A {}; A operator+(A&, int); A x; x + 1; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x + 1")); + + AST = tooling::buildASTFromCode( + "void f() { struct A { A(int&); }; int x; A y(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x")); + + AST = tooling::buildASTFromCode( + "void f() { struct A { A(); A(A&); }; A x; A y(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x")); +} + +TEST(ExprMutationAnalyzerTest, ByConstRefArgument) { + auto AST = tooling::buildASTFromCode( + "void g(const int&); void f() { int x; g(x); }"); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A {}; A operator+(const A&, int); A x; x + 1; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A { A(const int&); }; int x; A y(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A { A(); A(const A&); }; A x; A y(x); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, ByNonConstRRefArgument) { + auto AST = tooling::buildASTFromCode( + "void g(int&&); void f() { int x; g(static_cast(x)); }"); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("g(static_cast(x))")); + + AST = tooling::buildASTFromCode( + "void f() { struct A {}; A operator+(A&&, int); " + "A x; static_cast(x) + 1; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("static_cast(x) + 1")); + + AST = tooling::buildASTFromCode("void f() { struct A { A(int&&); }; " + "int x; A y(static_cast(x)); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("static_cast(x)")); + + AST = tooling::buildASTFromCode("void f() { struct A { A(); A(A&&); }; " + "A x; A y(static_cast(x)); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("static_cast(x)")); +} + +TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) { + auto AST = tooling::buildASTFromCode( + "void g(const int&&); void f() { int x; g(static_cast(x)); }"); + auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode( + "void f() { struct A {}; A operator+(const A&&, int); " + "A x; static_cast(x) + 1; }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode("void f() { struct A { A(const int&&); }; " + "int x; A y(static_cast(x)); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); + + AST = tooling::buildASTFromCode("void f() { struct A { A(); A(const A&&); }; " + "A x; A y(static_cast(x)); }"); + Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, Move) { + // Technically almost the same as ByNonConstRRefArgument, just double checking + const auto AST = tooling::buildASTFromCode( + "namespace std {" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template typename std::remove_reference::type&& " + "move(T&& t) noexcept; }" + "void f() { struct A {}; A x; std::move(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("std::move(x)")); +} + +TEST(ExprMutationAnalyzerTest, Forward) { + // Technically almost the same as ByNonConstRefArgument, just double checking + const auto AST = tooling::buildASTFromCode( + "namespace std {" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template struct remove_reference { typedef T type; };" + "template T&& " + "forward(typename std::remove_reference::type&) noexcept;" + "template T&& " + "forward(typename std::remove_reference::type&&) noexcept;" + "void f() { struct A {}; A x; std::forward(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("std::forward(x)")); +} + +TEST(ExprMutationAnalyzerTest, ReturnAsValue) { + const auto AST = tooling::buildASTFromCode("int f() { int x; return x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRef) { + const auto AST = tooling::buildASTFromCode("int& f() { int x; return x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("return x;")); +} + +TEST(ExprMutationAnalyzerTest, ReturnAsConstRef) { + const auto AST = + tooling::buildASTFromCode("const int& f() { int x; return x; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, ReturnAsNonConstRRef) { + const auto AST = tooling::buildASTFromCode( + "int&& f() { int x; return static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("return static_cast(x);")); +} + +TEST(ExprMutationAnalyzerTest, ReturnAsConstRRef) { + const auto AST = tooling::buildASTFromCode( + "const int&& f() { int x; return static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, TakeAddress) { + const auto AST = + tooling::buildASTFromCode("void g(int*); void f() { int x; g(&x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("&x")); +} + +TEST(ExprMutationAnalyzerTest, ArrayToPointerDecay) { + const auto AST = + tooling::buildASTFromCode("void g(int*); void f() { int x[2]; g(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x")); +} + +TEST(ExprMutationAnalyzerTest, FollowRefModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; " + "int& r3 = r2; r3 = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("r0", "r1", "r2", "r3", "r3 = 10")); +} + +TEST(ExprMutationAnalyzerTest, FollowRefNotModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x; int& r0 = x; int& r1 = r0; int& r2 = r1; " + "int& r3 = r2; int& r4 = r3; int& r5 = r4;}"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, FollowConditionalRefModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x, y; bool b; int &r = b ? x : y; r = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("r", "r = 10")); +} + +TEST(ExprMutationAnalyzerTest, FollowConditionalRefNotModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x, y; bool b; int& r = b ? x : y; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, ArrayElementModified) { + const auto AST = + tooling::buildASTFromCode("void f() { int x[2]; x[0] = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x[0] = 10")); +} + +TEST(ExprMutationAnalyzerTest, ArrayElementNotModified) { + const auto AST = tooling::buildASTFromCode("void f() { int x[2]; x[0]; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, NestedMemberModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { struct A { int vi; }; struct B { A va; }; " + "struct C { B vb; }; C x; x.vb.va.vi = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x.vb.va.vi = 10")); +} + +TEST(ExprMutationAnalyzerTest, NestedMemberNotModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { struct A { int vi; }; struct B { A va; }; " + "struct C { B vb; }; C x; x.vb.va.vi; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CastToValue) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CastToRefModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x; static_cast(x) = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre("static_cast(x) = 10")); +} + +TEST(ExprMutationAnalyzerTest, CastToRefNotModified) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CastToConstRef) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x; static_cast(x); }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByValue) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [=]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByValue) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [x]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByRef) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [&]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre(ResultOf(removeSpace, "[&](){x=10;}"))); +} + +TEST(ExprMutationAnalyzerTest, LambdaExplicitCaptureByRef) { + const auto AST = + tooling::buildASTFromCode("void f() { int x; [&x]() { x = 10; }; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), + ElementsAre(ResultOf(removeSpace, "[&x](){x=10;}"))); +} + +TEST(ExprMutationAnalyzerTest, RangeForArrayByRefModified) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x[2]; for (int& e : x) e = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10")); +} + +TEST(ExprMutationAnalyzerTest, RangeForArrayByRefNotModified) { + const auto AST = + tooling::buildASTFromCode("void f() { int x[2]; for (int& e : x) e; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, RangeForArrayByValue) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x[2]; for (int e : x) e = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, RangeForArrayByConstRef) { + const auto AST = tooling::buildASTFromCode( + "void f() { int x[2]; for (const int& e : x) e; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefModified) { + const auto AST = + tooling::buildASTFromCode("struct V { int* begin(); int* end(); };" + "void f() { V x; for (int& e : x) e = 10; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("e", "e = 10")); +} + +TEST(ExprMutationAnalyzerTest, RangeForNonArrayByRefNotModified) { + const auto AST = + tooling::buildASTFromCode("struct V { int* begin(); int* end(); };" + "void f() { V x; for (int& e : x) e; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, RangeForNonArrayByValue) { + const auto AST = tooling::buildASTFromCode( + "struct V { const int* begin() const; const int* end() const; };" + "void f() { V x; for (int e : x) e; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, RangeForNonArrayByConstRef) { + const auto AST = tooling::buildASTFromCode( + "struct V { const int* begin() const; const int* end() const; };" + "void f() { V x; for (const int& e : x) e; }"); + const auto Results = + match(withEnclosingCompound(declRefTo("x")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +} // namespace test +} // namespace tidy +} // namespace clang