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