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 }