blob: 8f21678f0c9f1fc43c9f9d3073b0d5fa400f743b [file] [log] [blame]
//===--- ParentVirtualCallCheck.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 "ParentVirtualCallCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/FixIt.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
#include <cctype>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace bugprone {
using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>;
static bool isParentOf(const CXXRecordDecl &Parent,
const CXXRecordDecl &ThisClass) {
if (Parent.getCanonicalDecl() == ThisClass.getCanonicalDecl())
return true;
const CXXRecordDecl *ParentCanonicalDecl = Parent.getCanonicalDecl();
return ThisClass.bases_end() !=
llvm::find_if(ThisClass.bases(), [=](const CXXBaseSpecifier &Base) {
auto *BaseDecl = Base.getType()->getAsCXXRecordDecl();
assert(BaseDecl);
return ParentCanonicalDecl == BaseDecl->getCanonicalDecl();
});
}
static BasesVector getParentsByGrandParent(const CXXRecordDecl &GrandParent,
const CXXRecordDecl &ThisClass,
const CXXMethodDecl &MemberDecl) {
BasesVector Result;
for (const auto &Base : ThisClass.bases()) {
const auto *BaseDecl = Base.getType()->getAsCXXRecordDecl();
const CXXMethodDecl *ActualMemberDecl =
MemberDecl.getCorrespondingMethodInClass(BaseDecl);
if (!ActualMemberDecl)
continue;
// TypePtr is the nearest base class to ThisClass between ThisClass and
// GrandParent, where MemberDecl is overridden. TypePtr is the class the
// check proposes to fix to.
const Type *TypePtr = ActualMemberDecl->getThisType().getTypePtr();
const CXXRecordDecl *RecordDeclType = TypePtr->getPointeeCXXRecordDecl();
assert(RecordDeclType && "TypePtr is not a pointer to CXXRecordDecl!");
if (RecordDeclType->getCanonicalDecl()->isDerivedFrom(&GrandParent))
Result.emplace_back(RecordDeclType);
}
return Result;
}
static std::string getNameAsString(const NamedDecl *Decl) {
std::string QualName;
llvm::raw_string_ostream OS(QualName);
PrintingPolicy PP(Decl->getASTContext().getPrintingPolicy());
PP.SuppressUnwrittenScope = true;
Decl->printQualifiedName(OS, PP);
return OS.str();
}
// Returns E as written in the source code. Used to handle 'using' and
// 'typedef'ed names of grand-parent classes.
static std::string getExprAsString(const clang::Expr &E,
clang::ASTContext &AC) {
std::string Text = tooling::fixit::getText(E, AC).str();
Text.erase(
llvm::remove_if(
Text,
[](char C) { return llvm::isSpace(static_cast<unsigned char>(C)); }),
Text.end());
return Text;
}
void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
traverse(
TK_AsIs,
cxxMemberCallExpr(
callee(memberExpr(hasDescendant(implicitCastExpr(
hasImplicitDestinationType(pointsTo(
type(anything()).bind("castToType"))),
hasSourceExpression(cxxThisExpr(hasType(
type(anything()).bind("thisType")))))))
.bind("member")),
callee(cxxMethodDecl(isVirtual())))),
this);
}
void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member");
assert(Member);
if (!Member->getQualifier())
return;
const auto *MemberDecl = cast<CXXMethodDecl>(Member->getMemberDecl());
const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>("thisType");
assert(ThisTypePtr);
const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();
assert(ThisType);
const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>("castToType");
assert(CastToTypePtr);
const auto *CastToType = CastToTypePtr->getAsCXXRecordDecl();
assert(CastToType);
if (isParentOf(*CastToType, *ThisType))
return;
const BasesVector Parents =
getParentsByGrandParent(*CastToType, *ThisType, *MemberDecl);
if (Parents.empty())
return;
std::string ParentsStr;
ParentsStr.reserve(30 * Parents.size());
for (const CXXRecordDecl *Parent : Parents) {
if (!ParentsStr.empty())
ParentsStr.append(" or ");
ParentsStr.append("'").append(getNameAsString(Parent)).append("'");
}
assert(Member->getQualifierLoc().getSourceRange().getBegin().isValid());
auto Diag = diag(Member->getQualifierLoc().getSourceRange().getBegin(),
"qualified name '%0' refers to a member overridden "
"in %plural{1:subclass|:subclasses}1; did you mean %2?")
<< getExprAsString(*Member, *Result.Context)
<< static_cast<unsigned>(Parents.size()) << ParentsStr;
// Propose a fix if there's only one parent class...
if (Parents.size() == 1 &&
// ...unless parent class is templated
!isa<ClassTemplateSpecializationDecl>(Parents.front()))
Diag << FixItHint::CreateReplacement(
Member->getQualifierLoc().getSourceRange(),
getNameAsString(Parents.front()) + "::");
}
} // namespace bugprone
} // namespace tidy
} // namespace clang