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 }