File indexing completed on 2025-04-06 05:21:27
0001 /* 0002 SPDX-FileCopyrightText: 2015 Klarälvdalens Datakonsult AB a KDAB Group company info@kdab.com 0003 SPDX-FileContributor: Sérgio Martins <sergio.martins@kdab.com> 0004 0005 SPDX-FileCopyrightText: 2015 Sergio Martins <smartins@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "detaching-member.h" 0011 #include "ClazyContext.h" 0012 #include "HierarchyUtils.h" 0013 #include "StringUtils.h" 0014 #include "Utils.h" 0015 #include "checkbase.h" 0016 #include "clazy_stl.h" 0017 0018 #include <clang/AST/Decl.h> 0019 #include <clang/AST/DeclCXX.h> 0020 #include <clang/AST/Expr.h> 0021 #include <clang/AST/ExprCXX.h> 0022 #include <clang/AST/Stmt.h> 0023 #include <clang/AST/Type.h> 0024 #include <clang/Basic/LLVM.h> 0025 #include <llvm/ADT/StringRef.h> 0026 #include <llvm/Support/Casting.h> 0027 0028 #include <vector> 0029 0030 using namespace clang; 0031 0032 DetachingMember::DetachingMember(const std::string &name, ClazyContext *context) 0033 : DetachingBase(name, context, Option_CanIgnoreIncludes) 0034 { 0035 m_filesToIgnore = {"qstring.h"}; 0036 } 0037 0038 void DetachingMember::VisitStmt(clang::Stmt *stm) 0039 { 0040 auto *callExpr = dyn_cast<CallExpr>(stm); 0041 if (!callExpr) { 0042 return; 0043 } 0044 0045 auto *memberCall = dyn_cast<CXXMemberCallExpr>(callExpr); 0046 auto *operatorExpr = dyn_cast<CXXOperatorCallExpr>(callExpr); 0047 if (!memberCall && !operatorExpr) { 0048 return; 0049 } 0050 0051 if (shouldIgnoreFile(stm->getBeginLoc())) { 0052 return; 0053 } 0054 0055 CXXMethodDecl *method = nullptr; 0056 ValueDecl *memberDecl = nullptr; 0057 if (operatorExpr) { 0058 FunctionDecl *func = operatorExpr->getDirectCallee(); 0059 method = func ? dyn_cast<CXXMethodDecl>(func) : nullptr; 0060 if (!method || clazy::name(method) != "operator[]") { 0061 return; 0062 } 0063 0064 auto *memberExpr = clazy::getFirstParentOfType<CXXMemberCallExpr>(m_context->parentMap, operatorExpr); 0065 CXXMethodDecl *parentMemberDecl = memberExpr ? memberExpr->getMethodDecl() : nullptr; 0066 if (parentMemberDecl && !parentMemberDecl->isConst()) { 0067 // Don't warn for s.m_listOfValues[0].nonConstMethod(); 0068 // However do warn for: s.m_listOfPointers[0]->nonConstMethod(); because it compiles with .at() 0069 QualType qt = operatorExpr->getType(); 0070 const Type *t = qt.getTypePtrOrNull(); 0071 if (t && !t->isPointerType()) { 0072 return; 0073 } 0074 } 0075 0076 memberDecl = Utils::valueDeclForOperatorCall(operatorExpr); 0077 } else { 0078 method = memberCall->getMethodDecl(); 0079 memberDecl = Utils::valueDeclForMemberCall(memberCall); 0080 } 0081 0082 if (!method || !memberDecl || !Utils::isMemberVariable(memberDecl) || !isDetachingMethod(method, DetachingMethodWithConstCounterPart) 0083 || method->isConst()) { 0084 return; 0085 } 0086 0087 // Catch cases like m_foo[0] = .. , which is fine 0088 0089 auto *parentUnaryOp = clazy::getFirstParentOfType<UnaryOperator>(m_context->parentMap, callExpr); 0090 if (parentUnaryOp) { 0091 // m_foo[0]++ is OK 0092 return; 0093 } 0094 0095 auto *parentOp = clazy::getFirstParentOfType<CXXOperatorCallExpr>(m_context->parentMap, clazy::parent(m_context->parentMap, callExpr)); 0096 if (parentOp) { 0097 FunctionDecl *parentFunc = parentOp->getDirectCallee(); 0098 const std::string parentFuncName = parentFunc ? parentFunc->getNameAsString() : ""; 0099 if (clazy::startsWith(parentFuncName, "operator")) { 0100 // m_foo[0] = ... is OK 0101 return; 0102 } 0103 } 0104 0105 auto *parentBinaryOp = clazy::getFirstParentOfType<BinaryOperator>(m_context->parentMap, callExpr); 0106 if (parentBinaryOp && parentBinaryOp->isAssignmentOp()) { 0107 // m_foo[0] += .. is OK 0108 Expr *lhs = parentBinaryOp->getLHS(); 0109 if (callExpr == lhs || clazy::isChildOf(callExpr, lhs)) { 0110 return; 0111 } 0112 } 0113 0114 const bool returnsNonConstIterator = memberCall && clazy::endsWith(memberCall->getType().getAsString(lo()), "iterator"); 0115 if (returnsNonConstIterator) { 0116 // If we're calling begin()/end() as arguments to a function taking non-const iterators it's fine 0117 // Such as qSort(list.begin(), list.end()); 0118 auto *parentCall = clazy::getFirstParentOfType<CallExpr>(m_context->parentMap, clazy::parent(m_context->parentMap, memberCall)); 0119 FunctionDecl *parentFunc = parentCall ? parentCall->getDirectCallee() : nullptr; 0120 if (parentFunc && parentFunc->getNumParams() == parentCall->getNumArgs()) { 0121 int i = 0; 0122 for (auto *argExpr : parentCall->arguments()) { 0123 auto expr2 = dyn_cast<CXXMemberCallExpr>(argExpr); // C++17 0124 if (!expr2) 0125 expr2 = clazy::getFirstChildOfType<CXXMemberCallExpr>(argExpr); // C++14 0126 if (expr2) { 0127 if (expr2 == memberCall) { 0128 // Success, we found which arg 0129 ParmVarDecl *parm = parentFunc->getParamDecl(i); 0130 0131 // Check that the record declarations have the same type 0132 if (parm->getType().getTypePtr()->getAsRecordDecl()->getNameAsString() 0133 == memberCall->getType().getTypePtr()->getAsRecordDecl()->getNameAsString()) { 0134 return; 0135 } 0136 break; 0137 } 0138 } 0139 ++i; 0140 } 0141 } 0142 } 0143 0144 emitWarning(stm->getBeginLoc(), "Potential detachment due to calling " + method->getQualifiedNameAsString() + "()"); 0145 }