File indexing completed on 2024-05-12 05:41:01

0001 /*
0002     SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "rule-of-three.h"
0008 #include "MacroUtils.h"
0009 #include "TypeUtils.h"
0010 #include "Utils.h"
0011 #include "clazy_stl.h"
0012 
0013 #include <clang/AST/DeclBase.h>
0014 #include <clang/AST/DeclCXX.h>
0015 #include <clang/Basic/LLVM.h>
0016 #include <clang/Basic/SourceLocation.h>
0017 #include <clang/Basic/SourceManager.h>
0018 #include <clang/Basic/Specifiers.h>
0019 #include <llvm/ADT/StringRef.h>
0020 #include <llvm/Support/Casting.h>
0021 
0022 #include <vector>
0023 
0024 class ClazyContext;
0025 
0026 using namespace clang;
0027 
0028 RuleOfThree::RuleOfThree(const std::string &name, ClazyContext *context)
0029     : RuleOfBase(name, context)
0030 {
0031     m_filesToIgnore = {"qrc_"};
0032 }
0033 
0034 void RuleOfThree::VisitDecl(clang::Decl *decl)
0035 {
0036     auto *record = dyn_cast<CXXRecordDecl>(decl);
0037     if (!record || isBlacklisted(record) || !record->hasDefinition() || record->isPolymorphic()) {
0038         return;
0039     }
0040 
0041     // fwd decl is not interesting
0042     if (record != record->getDefinition()) {
0043         return;
0044     }
0045 
0046     if (shouldIgnoreFile(decl->getBeginLoc())) {
0047         return;
0048     }
0049 
0050     const SourceLocation recordStart = record->getBeginLoc();
0051     if (recordStart.isMacroID()) {
0052         if (clazy::isInMacro(&m_astContext, recordStart, "Q_GLOBAL_STATIC_INTERNAL")) {
0053             return;
0054         }
0055     }
0056 
0057     CXXConstructorDecl *copyCtor = Utils::copyCtor(record);
0058     CXXMethodDecl *copyAssign = Utils::copyAssign(record);
0059     CXXDestructorDecl *destructor = record->getDestructor();
0060     const bool dtorDefaultedByUser = destructor && destructor->isDefaulted() && !destructor->isImplicit();
0061 
0062     const bool hasUserCopyCtor = copyCtor && copyCtor->isUserProvided();
0063     const bool hasUserCopyAssign = copyAssign && copyAssign->isUserProvided();
0064     const bool hasUserDtor = destructor && destructor->isUserProvided();
0065 
0066     const bool copyCtorIsDeleted = copyCtor && copyCtor->isDeleted();
0067     const bool copyAssignIsDeleted = copyAssign && copyAssign->isDeleted();
0068 
0069     bool hasImplicitDeletedCopy = false;
0070     if (!copyCtor || !copyAssign) {
0071         for (auto *f : record->fields()) {
0072             QualType qt = f->getType();
0073             if (qt.isConstQualified() || qt->isRValueReferenceType()) {
0074                 hasImplicitDeletedCopy = true;
0075                 break;
0076             }
0077         }
0078     }
0079 
0080     if (hasUserDtor && (copyCtorIsDeleted || copyAssignIsDeleted || hasImplicitDeletedCopy)) {
0081         // One of the copy methods was explicitely deleted, it's safe.
0082         // The case we want to catch is when one is user-written and the other is
0083         // compiler-generated.
0084         return;
0085     }
0086 
0087     const int numImplemented = hasUserCopyCtor + hasUserCopyAssign + hasUserDtor;
0088     if (numImplemented == 0 || numImplemented == 3) { // Rule of 3 respected
0089         return;
0090     }
0091 
0092     std::vector<StringRef> hasList;
0093     std::vector<StringRef> missingList;
0094     if (hasUserDtor) {
0095         hasList.push_back("dtor");
0096     } else {
0097         missingList.push_back("dtor");
0098     }
0099 
0100     if (hasUserCopyCtor) {
0101         hasList.push_back("copy-ctor");
0102     } else {
0103         missingList.push_back("copy-ctor");
0104     }
0105 
0106     if (hasUserCopyAssign) {
0107         hasList.push_back("copy-assignment");
0108     } else {
0109         missingList.push_back("copy-assignment");
0110     }
0111 
0112     const int numNotImplemented = missingList.size();
0113 
0114     if (hasUserDtor && numImplemented == 1) {
0115         // Protected dtor is a way for a non-polymorphic base class avoid being deleted
0116         if (destructor->getAccess() == clang::AccessSpecifier::AS_protected) {
0117             return;
0118         }
0119 
0120         if (Utils::functionHasEmptyBody(destructor)) {
0121             // Lets reduce noise and allow the empty dtor. In theory we could warn, but there's no
0122             // hidden bug behind this dummy dtor.
0123             return;
0124         }
0125     }
0126 
0127     if (!hasUserDtor && (clazy::derivesFrom(record, "QSharedData") || dtorDefaultedByUser)) {
0128         return;
0129     }
0130 
0131     if (Utils::hasMember(record, "QSharedDataPointer")) {
0132         return; // These need boiler-plate copy ctor and dtor
0133     }
0134 
0135     const std::string className = record->getNameAsString();
0136     const std::string classQualifiedName = record->getQualifiedNameAsString();
0137     const std::string filename = static_cast<std::string>(sm().getFilename(recordStart));
0138     if (clazy::endsWith(className, "Private") && clazy::endsWithAny(filename, {".cpp", ".cxx", "_p.h"})) {
0139         return; // Lots of RAII classes fall into this category. And even Private (d-pointer) classes, warning in that case would just be noise
0140     }
0141 
0142     std::string msg = classQualifiedName + " has ";
0143 
0144     for (int i = 0; i < numImplemented; ++i) {
0145         msg += hasList[i];
0146         const bool isLast = i == numImplemented - 1;
0147         if (!isLast) {
0148             msg += ',';
0149         }
0150         msg += ' ';
0151     }
0152 
0153     msg += "but not ";
0154     for (int i = 0; i < numNotImplemented; ++i) {
0155         msg += missingList[i];
0156         const bool isLast = i == numNotImplemented - 1;
0157         if (!isLast) {
0158             msg += ", ";
0159         }
0160     }
0161 
0162     emitWarning(decl->getBeginLoc(), msg);
0163 }