148 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "ParentVirtualCallCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/Tooling/FixIt.h"13#include "llvm/ADT/STLExtras.h"14#include "llvm/ADT/SmallVector.h"15#include <cctype>16 17using namespace clang::ast_matchers;18 19namespace clang::tidy::bugprone {20 21using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>;22 23static bool isParentOf(const CXXRecordDecl &Parent,24 const CXXRecordDecl &ThisClass) {25 if (Parent.getCanonicalDecl() == ThisClass.getCanonicalDecl())26 return true;27 const CXXRecordDecl *ParentCanonicalDecl = Parent.getCanonicalDecl();28 return llvm::any_of(ThisClass.bases(), [=](const CXXBaseSpecifier &Base) {29 auto *BaseDecl = Base.getType()->getAsCXXRecordDecl();30 assert(BaseDecl);31 return ParentCanonicalDecl == BaseDecl->getCanonicalDecl();32 });33}34 35static BasesVector getParentsByGrandParent(const CXXRecordDecl &GrandParent,36 const CXXRecordDecl &ThisClass,37 const CXXMethodDecl &MemberDecl) {38 BasesVector Result;39 for (const auto &Base : ThisClass.bases()) {40 const auto *BaseDecl = Base.getType()->getAsCXXRecordDecl();41 const CXXMethodDecl *ActualMemberDecl =42 MemberDecl.getCorrespondingMethodInClass(BaseDecl);43 if (!ActualMemberDecl)44 continue;45 // TypePtr is the nearest base class to ThisClass between ThisClass and46 // GrandParent, where MemberDecl is overridden. TypePtr is the class the47 // check proposes to fix to.48 const Type *TypePtr = ActualMemberDecl->getThisType().getTypePtr();49 const CXXRecordDecl *RecordDeclType = TypePtr->getPointeeCXXRecordDecl();50 assert(RecordDeclType && "TypePtr is not a pointer to CXXRecordDecl!");51 if (RecordDeclType->getCanonicalDecl()->isDerivedFrom(&GrandParent))52 Result.emplace_back(RecordDeclType);53 }54 55 return Result;56}57 58static std::string getNameAsString(const NamedDecl *Decl) {59 std::string QualName;60 llvm::raw_string_ostream OS(QualName);61 PrintingPolicy PP(Decl->getASTContext().getPrintingPolicy());62 PP.SuppressUnwrittenScope = true;63 Decl->printQualifiedName(OS, PP);64 return OS.str();65}66 67// Returns E as written in the source code. Used to handle 'using' and68// 'typedef'ed names of grand-parent classes.69static std::string getExprAsString(const clang::Expr &E,70 clang::ASTContext &AC) {71 std::string Text = tooling::fixit::getText(E, AC).str();72 llvm::erase_if(Text, [](char C) {73 return llvm::isSpace(static_cast<unsigned char>(C));74 });75 return Text;76}77 78void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {79 Finder->addMatcher(80 traverse(81 TK_AsIs,82 cxxMemberCallExpr(83 callee(memberExpr(hasDescendant(implicitCastExpr(84 hasImplicitDestinationType(pointsTo(85 type(anything()).bind("castToType"))),86 hasSourceExpression(cxxThisExpr(hasType(87 type(anything()).bind("thisType")))))))88 .bind("member")),89 callee(cxxMethodDecl(isVirtual())))),90 this);91}92 93void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {94 const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member");95 assert(Member);96 97 if (!Member->getQualifier())98 return;99 100 const auto *MemberDecl = cast<CXXMethodDecl>(Member->getMemberDecl());101 102 const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>("thisType");103 assert(ThisTypePtr);104 105 const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();106 assert(ThisType);107 108 const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>("castToType");109 assert(CastToTypePtr);110 111 const auto *CastToType = CastToTypePtr->getAsCXXRecordDecl();112 assert(CastToType);113 114 if (isParentOf(*CastToType, *ThisType))115 return;116 117 const BasesVector Parents =118 getParentsByGrandParent(*CastToType, *ThisType, *MemberDecl);119 120 if (Parents.empty())121 return;122 123 std::string ParentsStr;124 ParentsStr.reserve(30 * Parents.size());125 for (const CXXRecordDecl *Parent : Parents) {126 if (!ParentsStr.empty())127 ParentsStr.append(" or ");128 ParentsStr.append("'").append(getNameAsString(Parent)).append("'");129 }130 131 assert(Member->getQualifierLoc().getSourceRange().getBegin().isValid());132 auto Diag = diag(Member->getQualifierLoc().getSourceRange().getBegin(),133 "qualified name '%0' refers to a member overridden "134 "in %plural{1:subclass|:subclasses}1; did you mean %2?")135 << getExprAsString(*Member, *Result.Context)136 << static_cast<unsigned>(Parents.size()) << ParentsStr;137 138 // Propose a fix if there's only one parent class...139 if (Parents.size() == 1 &&140 // ...unless parent class is templated141 !isa<ClassTemplateSpecializationDecl>(Parents.front()))142 Diag << FixItHint::CreateReplacement(143 Member->getQualifierLoc().getSourceRange(),144 getNameAsString(Parents.front()) + "::");145}146 147} // namespace clang::tidy::bugprone148