File indexing completed on 2024-05-12 05:40:59

0001 /*
0002     SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "copyable-polymorphic.h"
0008 #include "AccessSpecifierManager.h"
0009 #include "ClazyContext.h"
0010 #include "FixItUtils.h"
0011 #include "StringUtils.h"
0012 #include "Utils.h"
0013 
0014 #include <clang/AST/DeclCXX.h>
0015 #include <clang/Basic/LLVM.h>
0016 #include <clang/Basic/Specifiers.h>
0017 #include <llvm/Support/Casting.h>
0018 
0019 class ClazyContext;
0020 namespace clang
0021 {
0022 class Decl;
0023 } // namespace clang
0024 
0025 using namespace clang;
0026 
0027 /// Returns whether the class has non-private copy-ctor or copy-assign
0028 static bool hasPublicCopy(const CXXRecordDecl *record)
0029 {
0030     CXXConstructorDecl *copyCtor = Utils::copyCtor(record);
0031     const bool hasCallableCopyCtor = copyCtor && !copyCtor->isDeleted() && copyCtor->getAccess() == clang::AS_public;
0032     if (!hasCallableCopyCtor) {
0033         CXXMethodDecl *copyAssign = Utils::copyAssign(record);
0034         const bool hasCallableCopyAssign = copyAssign && !copyAssign->isDeleted() && copyAssign->getAccess() == clang::AS_public;
0035         if (!hasCallableCopyAssign) {
0036             return false;
0037         }
0038     }
0039 
0040     return true;
0041 }
0042 
0043 /// Checks if there's any base class with public copy
0044 static bool hasPublicCopyInAncestors(const CXXRecordDecl *record)
0045 {
0046     if (!record) {
0047         return false;
0048     }
0049 
0050     for (auto base : record->bases()) {
0051         if (const Type *t = base.getType().getTypePtrOrNull()) {
0052             CXXRecordDecl *baseRecord = t->getAsCXXRecordDecl();
0053             if (hasPublicCopy(baseRecord)) {
0054                 return true;
0055             }
0056             if (hasPublicCopyInAncestors(t->getAsCXXRecordDecl())) {
0057                 return true;
0058             }
0059         }
0060     }
0061 
0062     return false;
0063 }
0064 
0065 CopyablePolymorphic::CopyablePolymorphic(const std::string &name, ClazyContext *context)
0066     : CheckBase(name, context)
0067 {
0068     context->enableAccessSpecifierManager();
0069 }
0070 
0071 void CopyablePolymorphic::VisitDecl(clang::Decl *decl)
0072 {
0073     auto *record = dyn_cast<CXXRecordDecl>(decl);
0074     if (!record || !record->hasDefinition() || record->getDefinition() != record || !record->isPolymorphic()) {
0075         return;
0076     }
0077 
0078     if (!hasPublicCopy(record)) {
0079         return;
0080     }
0081 
0082     if (record->isEffectivelyFinal() && !hasPublicCopyInAncestors(record)) {
0083         // If the derived class is final, and all the base classes copy-ctors are protected or private then it's ok
0084         return;
0085     }
0086 
0087     emitWarning(record->getBeginLoc(), "Polymorphic class " + record->getQualifiedNameAsString() + " is copyable. Potential slicing.", fixits(record));
0088 }
0089 
0090 std::vector<clang::FixItHint> CopyablePolymorphic::fixits(clang::CXXRecordDecl *record)
0091 {
0092     std::vector<FixItHint> result;
0093     if (!m_context->accessSpecifierManager) {
0094         return {};
0095     }
0096 
0097     const StringRef className = clazy::name(record);
0098 
0099     // Insert Q_DISABLE_COPY(classname) in the private section if one exists,
0100     // otherwise at the end of the class declaration
0101     SourceLocation pos = m_context->accessSpecifierManager->firstLocationOfSection(clang::AccessSpecifier::AS_private, record);
0102 
0103     if (pos.isValid()) {
0104         pos = Lexer::findLocationAfterToken(pos, clang::tok::colon, sm(), lo(), false);
0105         result.push_back(clazy::createInsertion(pos, std::string("\n\tQ_DISABLE_COPY(") + className.data() + std::string(")")));
0106     } else {
0107         pos = record->getBraceRange().getEnd();
0108         result.push_back(clazy::createInsertion(pos, std::string("\tQ_DISABLE_COPY(") + className.data() + std::string(")\n")));
0109     }
0110 
0111     // If the class has a default constructor, then we need to readd it,
0112     // as the disabled copy constructor removes it.
0113     // Add it in the public section if one exists, otherwise add a
0114     // public section at the top of the class declaration.
0115     if (record->hasDefaultConstructor()) {
0116         pos = m_context->accessSpecifierManager->firstLocationOfSection(clang::AccessSpecifier::AS_public, record);
0117         if (pos.isInvalid()) {
0118             pos = record->getBraceRange().getBegin().getLocWithOffset(1);
0119             result.push_back(clazy::createInsertion(pos, std::string("\npublic:\n\t") + className.data() + std::string("() = default;")));
0120         } else {
0121             pos = Lexer::findLocationAfterToken(pos, clang::tok::colon, sm(), lo(), false);
0122             result.push_back(clazy::createInsertion(pos, std::string("\n\t") + className.data() + std::string("() = default;")));
0123         }
0124     }
0125 
0126     return result;
0127 }