blob: 3e5b71fdd56b6e6ef74d8ae542cbc627b8e6ae74 [file] [log] [blame]
//===--- MakeMemberFunctionConstCheck.cpp - clang-tidy --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "MakeMemberFunctionConstCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace readability {
AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
return Node.hasAnyDependentBases();
}
AST_MATCHER(CXXMethodDecl, isTemplate) {
return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
}
AST_MATCHER(CXXMethodDecl, isDependentContext) {
return Node.isDependentContext();
}
AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) {
const ASTContext &Ctxt = Finder->getASTContext();
return clang::Lexer::makeFileCharRange(
clang::CharSourceRange::getCharRange(
Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()),
Ctxt.getSourceManager(), Ctxt.getLangOpts())
.isInvalid();
}
AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
}
enum UsageKind { Unused, Const, NonConst };
class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
ASTContext &Ctxt;
public:
FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}
UsageKind Usage = Unused;
template <class T> const T *getParent(const Expr *E) {
DynTypedNodeList Parents = Ctxt.getParents(*E);
if (Parents.size() != 1)
return nullptr;
return Parents.begin()->get<T>();
}
bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) {
// An UnresolvedMemberExpr might resolve to a non-const non-static
// member function.
Usage = NonConst;
return false; // Stop traversal.
}
bool VisitCXXConstCastExpr(const CXXConstCastExpr *) {
// Workaround to support the pattern
// class C {
// const S *get() const;
// S* get() {
// return const_cast<S*>(const_cast<const C*>(this)->get());
// }
// };
// Here, we don't want to make the second 'get' const even though
// it only calls a const member function on this.
Usage = NonConst;
return false; // Stop traversal.
}
// Our AST is
// `-ImplicitCastExpr
// (possibly `-UnaryOperator Deref)
// `-CXXThisExpr 'S *' this
bool visitUser(const ImplicitCastExpr *Cast) {
if (Cast->getCastKind() != CK_NoOp)
return false; // Stop traversal.
// Only allow NoOp cast to 'const S' or 'const S *'.
QualType QT = Cast->getType();
if (QT->isPointerType())
QT = QT->getPointeeType();
if (!QT.isConstQualified())
return false; // Stop traversal.
const auto *Parent = getParent<Stmt>(Cast);
if (!Parent)
return false; // Stop traversal.
if (isa<ReturnStmt>(Parent))
return true; // return (const S*)this;
if (isa<CallExpr>(Parent))
return true; // use((const S*)this);
// ((const S*)this)->Member
if (const auto *Member = dyn_cast<MemberExpr>(Parent))
return visitUser(Member, /*OnConstObject=*/true);
return false; // Stop traversal.
}
// If OnConstObject is true, then this is a MemberExpr using
// a constant this, i.e. 'const S' or 'const S *'.
bool visitUser(const MemberExpr *Member, bool OnConstObject) {
if (Member->isBoundMemberFunction(Ctxt)) {
if (!OnConstObject || Member->getFoundDecl().getAccess() != AS_public) {
// Non-public non-static member functions might not preserve the
// logical constness. E.g. in
// class C {
// int &data() const;
// public:
// int &get() { return data(); }
// };
// get() uses a private const method, but must not be made const
// itself.
return false; // Stop traversal.
}
// Using a public non-static const member function.
return true;
}
const auto *Parent = getParent<Expr>(Member);
if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {
// A read access to a member is safe when the member either
// 1) has builtin type (a 'const int' cannot be modified),
// 2) or it's a public member (the pointee of a public 'int * const' can
// can be modified by any user of the class).
if (Member->getFoundDecl().getAccess() != AS_public &&
!Cast->getType()->isBuiltinType())
return false;
if (Cast->getCastKind() == CK_LValueToRValue)
return true;
if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified())
return true;
}
if (const auto *M = dyn_cast_or_null<MemberExpr>(Parent))
return visitUser(M, /*OnConstObject=*/false);
return false; // Stop traversal.
}
bool VisitCXXThisExpr(const CXXThisExpr *E) {
Usage = Const;
const auto *Parent = getParent<Expr>(E);
// Look through deref of this.
if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) {
if (UnOp->getOpcode() == UO_Deref) {
Parent = getParent<Expr>(UnOp);
}
}
// It's okay to
// return (const S*)this;
// use((const S*)this);
// ((const S*)this)->f()
// when 'f' is a public member function.
if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {
if (visitUser(Cast))
return true;
// And it's also okay to
// (const T)(S->t)
// (LValueToRValue)(S->t)
// when 't' is either of builtin type or a public member.
} else if (const auto *Member = dyn_cast_or_null<MemberExpr>(Parent)) {
if (visitUser(Member, /*OnConstObject=*/false))
return true;
}
// Unknown user of this.
Usage = NonConst;
return false; // Stop traversal.
}
};
AST_MATCHER(CXXMethodDecl, usesThisAsConst) {
FindUsageOfThis UsageOfThis(Finder->getASTContext());
// TraverseStmt does not modify its argument.
UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody()));
return UsageOfThis.Usage == Const;
}
void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
traverse(
TK_AsIs,
cxxMethodDecl(
isDefinition(), isUserProvided(),
unless(anyOf(
isExpansionInSystemHeader(), isVirtual(), isConst(),
isStatic(), hasTrivialBody(), cxxConstructorDecl(),
cxxDestructorDecl(), isTemplate(), isDependentContext(),
ofClass(anyOf(isLambda(),
hasAnyDependentBases()) // Method might become
// virtual depending on
// template base class.
),
isInsideMacroDefinition(),
hasCanonicalDecl(isInsideMacroDefinition()))),
usesThisAsConst())
.bind("x")),
this);
}
static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) {
TypeSourceInfo *TSI = M->getTypeSourceInfo();
if (!TSI)
return {};
FunctionTypeLoc FTL =
TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
if (!FTL)
return {};
return FTL.getRParenLoc().getLocWithOffset(1);
}
void MakeMemberFunctionConstCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");
const auto *Declaration = Definition->getCanonicalDecl();
auto Diag = diag(Definition->getLocation(), "method %0 can be made const")
<< Definition
<< FixItHint::CreateInsertion(getConstInsertionPoint(Definition),
" const");
if (Declaration != Definition) {
Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration),
" const");
}
}
} // namespace readability
} // namespace tidy
} // namespace clang