File indexing completed on 2024-05-19 05:41:44

0001 /*
0002     SPDX-FileCopyrightText: 2017 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "thread-with-slots.h"
0008 #include "AccessSpecifierManager.h"
0009 #include "ClazyContext.h"
0010 #include "HierarchyUtils.h"
0011 #include "QtUtils.h"
0012 #include "TypeUtils.h"
0013 
0014 #include <clang/AST/Decl.h>
0015 #include <clang/AST/DeclCXX.h>
0016 #include <clang/AST/Expr.h>
0017 #include <clang/AST/Stmt.h>
0018 #include <clang/Basic/LLVM.h>
0019 #include <llvm/ADT/StringRef.h>
0020 #include <llvm/Support/Casting.h>
0021 
0022 #include <vector>
0023 
0024 namespace clang
0025 {
0026 class Decl;
0027 } // namespace clang
0028 
0029 using namespace clang;
0030 
0031 static bool hasMutexes(Stmt *body)
0032 {
0033     auto declrefs = clazy::getStatements<DeclRefExpr>(body);
0034     for (auto *declref : declrefs) {
0035         ValueDecl *valueDecl = declref->getDecl();
0036         if (CXXRecordDecl *record = clazy::typeAsRecord(valueDecl->getType())) {
0037             if (clazy::name(record) == "QMutex" || clazy::name(record) == "QBasicMutex") {
0038                 return true;
0039             }
0040         }
0041     }
0042 
0043     return false;
0044 }
0045 
0046 ThreadWithSlots::ThreadWithSlots(const std::string &name, ClazyContext *context)
0047     : CheckBase(name, context)
0048 {
0049     context->enableAccessSpecifierManager();
0050 }
0051 
0052 void ThreadWithSlots::VisitStmt(clang::Stmt *stmt)
0053 {
0054     // Here we catch slots not marked as slots, we warn when the connect is made
0055 
0056     auto *callExpr = dyn_cast<CallExpr>(stmt);
0057     if (!callExpr || !m_context->accessSpecifierManager) {
0058         return;
0059     }
0060 
0061     FunctionDecl *connectFunc = callExpr->getDirectCallee();
0062     if (!clazy::isConnect(connectFunc)) {
0063         return;
0064     }
0065 
0066     CXXMethodDecl *slot = clazy::receiverMethodForConnect(callExpr);
0067     if (!slot || !clazy::derivesFrom(slot->getParent(), "QThread")) {
0068         return;
0069     }
0070 
0071     if (clazy::name(slot->getParent()) == "QThread") { // The slots in QThread are thread safe, we're only worried about derived classes
0072         return;
0073     }
0074 
0075     QtAccessSpecifierType specifierType = m_context->accessSpecifierManager->qtAccessSpecifierType(slot);
0076     if (specifierType == QtAccessSpecifier_Slot || specifierType == QtAccessSpecifier_Signal) {
0077         return; // For stuff explicitly marked as slots or signals we use VisitDecl
0078     }
0079 
0080     emitWarning(slot, "Slot " + slot->getQualifiedNameAsString() + " might not run in the expected thread");
0081 }
0082 
0083 void ThreadWithSlots::VisitDecl(Decl *decl)
0084 {
0085     // Here we catch slots marked as such, and warn when they are declared
0086 
0087     auto *method = dyn_cast<CXXMethodDecl>(decl);
0088     if (!method || !m_context->accessSpecifierManager || !method->isThisDeclarationADefinition() || !method->hasBody()
0089         || !clazy::derivesFrom(method->getParent(), "QThread")) {
0090         return;
0091     }
0092 
0093     // The slots in QThread are thread safe, we're only worried about derived classes:
0094     if (clazy::name(method->getParent()) == "QThread") {
0095         return;
0096     }
0097 
0098     // We're only interested in slots:
0099     if (m_context->accessSpecifierManager->qtAccessSpecifierType(method) != QtAccessSpecifier_Slot) {
0100         return;
0101     }
0102 
0103     // Look for a mutex, or mutex locker, to avoid some false-positives
0104     Stmt *body = method->getBody();
0105     if (hasMutexes(body)) {
0106         return;
0107     }
0108 
0109     // If we use member mutexes, let's not warn either
0110     bool accessesNonMutexMember = false;
0111     auto memberexprs = clazy::getStatements<MemberExpr>(body);
0112     for (auto *memberexpr : memberexprs) {
0113         ValueDecl *valueDecl = memberexpr->getMemberDecl();
0114         if (CXXRecordDecl *record = clazy::typeAsRecord(valueDecl->getType())) {
0115             if (clazy::name(record) == "QMutex" || clazy::name(record) == "QBasicMutex") {
0116                 return;
0117             }
0118         }
0119         accessesNonMutexMember = true;
0120     }
0121 
0122     if (!accessesNonMutexMember) {
0123         return;
0124     }
0125 
0126     emitWarning(method, "Slot " + method->getQualifiedNameAsString() + " might not run in the expected thread");
0127 }