mnbvmar created this revision. mnbvmar added a reviewer: alexfh. mnbvmar added subscribers: krystyna, sbarzowski, Prazek, staronj, cfe-commits.
This implements unnecessary-mutable check. It's still bug-prone and might produce false positives, so all suggestions are welcome. http://reviews.llvm.org/D20053 Files: clang-tidy/misc/CMakeLists.txt clang-tidy/misc/MiscTidyModule.cpp clang-tidy/misc/UnnecessaryMutableCheck.cpp clang-tidy/misc/UnnecessaryMutableCheck.h docs/clang-tidy/checks/list.rst docs/clang-tidy/checks/misc-unnecessary-mutable.rst test/clang-tidy/misc-unnecessary-mutable.cpp
Index: test/clang-tidy/misc-unnecessary-mutable.cpp =================================================================== --- /dev/null +++ test/clang-tidy/misc-unnecessary-mutable.cpp @@ -0,0 +1,354 @@ +// RUN: %check_clang_tidy %s misc-unnecessary-mutable %t + +struct NothingMutable { + int field1; + unsigned field2; + const int field3; + volatile float field4; + + NothingMutable(int a1, unsigned a2, int a3, float a4) : field1(a1), field2(a2), field3(a3), field4(a4) {} + + void doSomething() { + field1 = 1; + field2 = 2; + field4 = 4; + } +}; + +struct NoMethods { + int field1; + mutable unsigned field2; // These cannot be fixed; they're public + const int field3; + mutable volatile NothingMutable field4; +}; + +class NoMethodsClass { +public: + int field1; + mutable unsigned field2; + +private: + const int field3; + mutable volatile NothingMutable field4; + // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: 'mutable' modifier is unnecessary for field 'field4' [misc-unnecessary-mutable] + // CHECK-FIXES: {{^ }}volatile NothingMutable field4; +}; + +struct PrivateInStruct { +private: + mutable volatile unsigned long long blah; + // CHECK-MESSAGES: :[[@LINE-1]]:39: warning: 'mutable' modifier is unnecessary for field 'blah' {{..}} + // CHECK-FIXES: {{^ }}volatile unsigned long long blah; +}; + +union PrivateInUnion { +public: + int someField; + +private: + mutable char otherField; +}; + +class UnusedVar { +private: + mutable int x __attribute__((unused)); + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mutable' modifier is unnecessary for field 'x' {{..}} + // CHECK-FIXES: {{^ }}int x __attribute__((unused)); +}; + +class NoConstMethodsClass { +public: + int field1; + mutable unsigned field2; + + NoConstMethodsClass() : field2(42), field3(9), field4(NothingMutable(1, 2, 3, 4)) {} + + void doSomething() { + field2 = 8; + field1 = 99; + field4.doSomething(); + } + +private: + const int field3; + mutable NothingMutable field4; + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: 'mutable' modifier is unnecessary for field 'field4' {{..}} + // CHECK-FIXES: {{^ }}NothingMutable field4; +}; + +class ConstMethods { +private: + mutable int field1, field2; + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: 'mutable' modifier is unnecessary for field 'field2' {{..}} + mutable int incr, decr, set, mul, constArg1, constArg2, constRef, ref1, ref2; + // CHECK-MESSAGES: :[[@LINE-1]]:37: warning: 'mutable' modifier is unnecessary for field 'constArg1' {{..}} + // CHECK-MESSAGES: :[[@LINE-2]]:48: warning: 'mutable' modifier is unnecessary for field 'constArg2' {{..}} + // CHECK-MESSAGES: :[[@LINE-3]]:59: warning: 'mutable' modifier is unnecessary for field 'constRef' {{..}} + + void takeArg(int x) const { x *= 4; } + int takeConstRef(const int &x) const { return x + 99; } + void takeRef(int &) const {} + + template <typename... Args> + void takeArgs(Args... args) const {} + template <typename... Args> + void takeArgRefs(Args &... args) const {} + +public: + void doSomething() const { + field1 = field2; + } + + void doOtherThing() const { + incr++; + decr--; + set = 42; + mul *= 3; + takeArg(constArg1); + takeConstRef(constRef); + takeRef(ref1); + takeArgs(constArg1, constArg2); + takeArgRefs(ref1, ref2); + } +}; + +class NonFinalClass { +public: + mutable int fPublic; + +protected: + mutable int fProtected; + +private: + mutable int fPrivate; + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mutable' modifier is unnecessary for field 'fPrivate' {{..}} + // CHECK-FIXES: {{^ }}int fPrivate; +}; + +class FinalClass final { +public: + mutable int fPublic; + +protected: + mutable int fProtected; + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mutable' modifier is unnecessary for field 'fProtected' {{..}} + // CHECK-FIXES: {{^ }}int fProtected; + +private: + mutable int fPrivate; + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mutable' modifier is unnecessary for field 'fPrivate' {{..}} + // CHECK-FIXES: {{^ }}int fPrivate; +}; + +class NotAllFuncsKnown { + void doSomething(); + void doSomethingConst() const {} + +private: + mutable int field; + // Can't be fixed. We don't know if doSomething() doesn't declare a *const* NotAllFuncsKnown instance + // and then modify 'field' field. +}; + +class NotAllConstFuncsKnown { + void doSomething() {} + void doSomethingConst() const; + void doOtherConst() const {} + +private: + mutable int field; +}; + +class ConstFuncOutside { + void doSomething(); + void doSomethingConst() const; + void doOtherConst() const {} + +private: + mutable int field; + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 'mutable' modifier is unnecessary for field 'field' {{..}} + // CHECK-FIXES: {{^ }}int field; +}; + +void ConstFuncOutside::doSomethingConst() const {} + +// We can't really see if mutable is necessary or not. +template <typename T> +class TemplatedClass { +public: + void doSomething() { + a = b = c; + } + + void doSomethingConst() const { + a = b = c; + } + +private: + mutable int a, b, c; +}; + +TemplatedClass<int> TemplatedClassInt; + +class ClassWithTemplates { +public: + void doSomethingConst() const { + a = b; + } + + // Here, we can see that. + template <typename T> + void doOtherConst() const { + b = c; + } + +private: + mutable int a, b, c, d; + // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: 'mutable' modifier is unnecessary for field 'c' {{..}} + // CHECK-MESSAGES: :[[@LINE-2]]:24: warning: 'mutable' modifier is unnecessary for field 'd' {{..}} +}; + +class ImplicitCast { +public: + void doSomethingConst() const { + a = b; + } + +private: + mutable int a; + mutable double b; + // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 'mutable' modifier is unnecessary for field 'b' {{..}} + // CHECK_FIXES: {{^ }}double b; +}; + +struct MutableNotFirst { +private: + long mutable long abc = 42; + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: 'mutable' modifier is unnecessary for field 'abc' {{..}} + // CHECK_FIXES: {{^ }}long long abc; + long long mutable bca; + // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: 'mutable' modifier is unnecessary for field 'bca' {{..}} + // CHECK_FIXES: {{^ }}long long bca; + int mutable ca; + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 'mutable' modifier is unnecessary for field 'ca' {{..}} + // CHECK_FIXES: {{^ }}int ca; +}; + +// Fails for now. +/* +void change(const int &a) { + const_cast<int&>(a) = 42; +} + +struct Change { + void changeMember() const { + change(m); + const int& x = otherM; + const_cast<int&>(x) = 33; + + const_cast<int&>(anotherM) = -4; + } +private: + mutable int m; + mutable int otherM; + mutable int anotherM; +}; +*/ + +class StrangeClass { +public: + void foo() {} +}; + +void EvilFunction(int &a) {} +void EvilFunction(const int &a) {} + +class JustClass { +public: + JustClass() {} + + void foo() { + MutableClass.foo(); + } + + void foo() const; + void evilCaller() const; + +private: + mutable StrangeClass MutableClass; // Must stay mutable (because of foo func below) + mutable int MutableInt; // Must stay mutable +}; + +void JustClass::foo() const { + MutableClass.foo(); +} + +void JustClass::evilCaller() const { + EvilFunction(MutableInt); +} + +class AnotherStrangeClass { +public: + // Example of non-const method which requires that MutableInt should stay mutable. + void strangeFoo() { + const AnotherStrangeClass *ConstThis = this; + EvilFunction(ConstThis->MutableInt); + } + +private: + mutable int MutableInt; // must stay mutable +}; + +class ClassWithStrangeConstructor { +public: + ClassWithStrangeConstructor() { + const ClassWithStrangeConstructor *ConstThis = this; + EvilFunction(ConstThis->MutableInt); + } + +private: + mutable int MutableInt; // must stay mutable +}; + +class ClassWithStrangeDestructor { +public: + ~ClassWithStrangeDestructor() { + const ClassWithStrangeDestructor *ConstThis = this; + EvilFunction(ConstThis->MutableInt); + } + +private: + mutable int MutableInt; // must stay mutable +}; + +// Don't touch friends or they'll hurt you. +class ClassWithFriends { +public: + friend void someFriend(ClassWithFriends *); + +private: + mutable int MutableInt; // must stay mutable +}; + +void someFriend(ClassWithFriends *Class) { + const ClassWithFriends *ConstClass = Class; + EvilFunction(ConstClass->MutableInt); +} + +class ClassWithClassFriends { +public: + friend class ClassFriend; + +private: + mutable int MutableInt; // must stay mutable +}; + +class ClassFriend { +public: + static void foo(const ClassWithClassFriends *Class) { + EvilFunction(Class->MutableInt); + } + +private: + mutable int m; +}; Index: docs/clang-tidy/checks/misc-unnecessary-mutable.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/misc-unnecessary-mutable.rst @@ -0,0 +1,49 @@ +.. title:: clang-tidy - misc-unnecessary-mutable + +misc-unnecessary-mutable +======================== + +The tool tries to find situations where ``mutable`` might be unnecessary +(i.e. each use of the mutable field on the const object is constant). + +.. code-block:: c++ + class SomeClass { + public: + void foo() { + field2 = 42; + } + + void bar() const { + field3 = 99; + } + + // Should stay mutable. Everyone can change it. + mutable int field1; + + private: + // Might lose 'mutable'. It's never modified in const object. + mutable int field2; + + // Should stay mutable; bar() changes it even though 'this' is const. + mutable int field3; + }; + +It also can automatically remove ``mutable`` keyword. For now, it is done +only if non-necessarily mutable field is the only declaration in the statement. + +Please note that there is a possibility that false-positives occur. For example, +one can use ``const_cast``: + +.. code-block:: c++ + class AnotherClass { + public: + void foo() const { + const int& y = x; + const_cast<int&>(y) = 42; + } + + private: + mutable int x; // SHOULD stay mutable. For now, it does not. + }; + +The tool should be thus used with care. Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -85,6 +85,7 @@ misc-throw-by-value-catch-by-reference misc-undelegated-constructor misc-uniqueptr-reset-release + misc-unnecessary-mutable misc-unused-alias-decls misc-unused-parameters misc-unused-raii Index: clang-tidy/misc/UnnecessaryMutableCheck.h =================================================================== --- /dev/null +++ clang-tidy/misc/UnnecessaryMutableCheck.h @@ -0,0 +1,43 @@ +//===--- UnnecessaryMutableCheck.h - clang-tidy------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNNECESSARY_MUTABLE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNNECESSARY_MUTABLE_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Searches for and removes unnecessary 'mutable' keywords (that is, for +/// fields that cannot be possibly changed in const methods). +/// +/// Example: +/// +/// class MyClass { +/// mutable int field; // will change into "int field;" +/// }; +/// +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-unnecessary-mutable.html +class UnnecessaryMutableCheck : public ClangTidyCheck { +public: + UnnecessaryMutableCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_UNNECESSARY_MUTABLE_H Index: clang-tidy/misc/UnnecessaryMutableCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/misc/UnnecessaryMutableCheck.cpp @@ -0,0 +1,223 @@ +//===--- UnnecessaryMutableCheck.cpp - clang-tidy--------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryMutableCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ParentMap.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void UnnecessaryMutableCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + fieldDecl(anyOf(isPrivate(), allOf(isProtected(), + hasParent(cxxRecordDecl(isFinal())))), + unless(anyOf(isImplicit(), isInstantiated(), + hasParent(cxxRecordDecl(isUnion()))))) + .bind("field"), + this); +} + +class FieldUseVisitor : public RecursiveASTVisitor<FieldUseVisitor> { +public: + explicit FieldUseVisitor(FieldDecl *SoughtField) + : SoughtField(SoughtField), FoundNonConstUse(false) {} + + void RunSearch(Decl *Declaration) { + auto *Body = Declaration->getBody(); + ParMap = new ParentMap(Body); + TraverseStmt(Body); + delete ParMap; + } + + bool VisitExpr(Expr *GenericExpr) { + MemberExpr *Expression = dyn_cast<MemberExpr>(GenericExpr); + if (Expression == nullptr) + return true; + + if (Expression->getMemberDecl() != SoughtField) + return true; + + // Check if expr is a member of const thing. + bool IsConstObj = false; + for (auto *ChildStmt : Expression->children()) { + Expr *ChildExpr = dyn_cast<Expr>(ChildStmt); + if (ChildExpr) { + IsConstObj |= ChildExpr->getType().isConstQualified(); + + // If member expression dereferences, we need to check + // whether pointer type is const. + if (Expression->isArrow()) { + auto &WrappedType = *ChildExpr->getType(); + if (!WrappedType.isPointerType()) { + // It's something weird. Just to be sure, assume we're const. + IsConstObj = true; + } else { + IsConstObj |= WrappedType.getPointeeType().isConstQualified(); + } + } + } + } + + // If it's not, mutableness changes nothing. + if (!IsConstObj) + return true; + + // By a const operation on a member expression we mean a MemberExpr + // whose parent is ImplicitCastExpr to rvalue or something constant. + bool HasRvalueCast = false; + bool HasConstCast = false; + if (ParMap->hasParent(Expression)) { + const auto *Cast = + dyn_cast<ImplicitCastExpr>(ParMap->getParent(Expression)); + if (Cast != nullptr) { + HasRvalueCast = Cast->getCastKind() == CastKind::CK_LValueToRValue; + HasConstCast = Cast->getType().isConstQualified(); + } + } + + if (!HasRvalueCast && !HasConstCast) { + FoundNonConstUse = true; + return false; + } + + return true; + } + + bool IsNonConstUseFound() const { return FoundNonConstUse; } + +private: + FieldDecl *SoughtField; + ParentMap *ParMap; + bool FoundNonConstUse; +}; + +class ClassMethodVisitor : public RecursiveASTVisitor<ClassMethodVisitor> { +public: + ClassMethodVisitor(ASTContext &Context, FieldDecl *SoughtField) + : SM(Context.getSourceManager()), SoughtField(SoughtField), + NecessaryMutable(false) {} + + bool VisitDecl(Decl *GenericDecl) { + // As for now, we can't track friends. + if (dyn_cast<FriendDecl>(GenericDecl)) { + NecessaryMutable = true; + return false; + } + + FunctionDecl *Declaration = dyn_cast<FunctionDecl>(GenericDecl); + + if (Declaration == nullptr) + return true; + + // All decls need their definitions in main file. + if (!Declaration->hasBody() || + !SM.isInMainFile(Declaration->getBody()->getLocStart())) { + NecessaryMutable = true; + return false; + } + + FieldUseVisitor FieldVisitor(SoughtField); + FieldVisitor.RunSearch(Declaration); + if (FieldVisitor.IsNonConstUseFound()) { + NecessaryMutable = true; + return false; + } + + return true; + } + + bool IsMutableNecessary() const { return NecessaryMutable; } + +private: + SourceManager &SM; + FieldDecl *SoughtField; + bool NecessaryMutable; +}; + +// Checks if 'mutable' keyword can be removed; for now; we do it only if +// it is the only declaration in a declaration chain. +static bool CheckRemoval(SourceManager &SM, const SourceLocation &LocStart, + const SourceLocation &LocEnd, ASTContext &Context, + SourceRange &ResultRange) { + + FileID FID = SM.getFileID(LocEnd); + llvm::MemoryBuffer *Buffer = SM.getBuffer(FID, LocEnd); + Lexer DeclLexer(SM.getLocForStartOfFile(FID), Context.getLangOpts(), + Buffer->getBufferStart(), SM.getCharacterData(LocStart), + Buffer->getBufferEnd()); + + Token DeclToken; + bool result = false; + + while (!DeclLexer.LexFromRawLexer(DeclToken)) { + + if (DeclToken.getKind() == tok::TokenKind::semi) { + break; + } + + if (DeclToken.getKind() == tok::TokenKind::comma) { + return false; + } + + if (DeclToken.isOneOf(tok::TokenKind::identifier, + tok::TokenKind::raw_identifier)) { + auto TokenStr = DeclToken.getRawIdentifier().str(); + + // "mutable" cannot be used in any other way than to mark mutableness + if (TokenStr == "mutable") { + ResultRange = + SourceRange(DeclToken.getLocation(), DeclToken.getEndLoc()); + result = true; + } + } + } + + assert(result && "No mutable found, weird"); + + return result; +} + +void UnnecessaryMutableCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MD = Result.Nodes.getNodeAs<FieldDecl>("field"); + const auto *ClassMatch = dyn_cast<CXXRecordDecl>(MD->getParent()); + auto &Context = *Result.Context; + auto &SM = *Result.SourceManager; + + if (!MD->getDeclName() || ClassMatch->isDependentContext() || + !MD->isMutable()) + return; + + ClassMethodVisitor Visitor(Context, const_cast<FieldDecl *>(MD)); + Visitor.TraverseDecl(const_cast<CXXRecordDecl *>(ClassMatch)); + + if (Visitor.IsMutableNecessary()) + return; + + auto Diag = + diag(MD->getLocation(), "'mutable' modifier is unnecessary for field %0") + << MD->getDeclName(); + + SourceRange RemovalRange; + + if (CheckRemoval(SM, MD->getLocStart(), MD->getLocEnd(), Context, + RemovalRange)) { + Diag << FixItHint::CreateRemoval(RemovalRange); + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang Index: clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tidy/misc/MiscTidyModule.cpp +++ clang-tidy/misc/MiscTidyModule.cpp @@ -45,6 +45,7 @@ #include "ThrowByValueCatchByReferenceCheck.h" #include "UndelegatedConstructor.h" #include "UniqueptrResetReleaseCheck.h" +#include "UnnecessaryMutableCheck.h" #include "UnusedAliasDeclsCheck.h" #include "UnusedParametersCheck.h" #include "UnusedRAIICheck.h" @@ -126,6 +127,8 @@ "misc-undelegated-constructor"); CheckFactories.registerCheck<UniqueptrResetReleaseCheck>( "misc-uniqueptr-reset-release"); + CheckFactories.registerCheck<UnnecessaryMutableCheck>( + "misc-unnecessary-mutable"); CheckFactories.registerCheck<UnusedAliasDeclsCheck>( "misc-unused-alias-decls"); CheckFactories.registerCheck<UnusedParametersCheck>( Index: clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tidy/misc/CMakeLists.txt +++ clang-tidy/misc/CMakeLists.txt @@ -37,6 +37,7 @@ ThrowByValueCatchByReferenceCheck.cpp UndelegatedConstructor.cpp UniqueptrResetReleaseCheck.cpp + UnnecessaryMutableCheck.cpp UnusedAliasDeclsCheck.cpp UnusedParametersCheck.cpp UnusedRAIICheck.cpp
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits