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

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 "old-style-connect.h"
0011 #include "AccessSpecifierManager.h"
0012 #include "ClazyContext.h"
0013 #include "ContextUtils.h"
0014 #include "FixItUtils.h"
0015 #include "HierarchyUtils.h"
0016 #include "QtUtils.h"
0017 #include "StringUtils.h"
0018 #include "Utils.h"
0019 #include "clazy_stl.h"
0020 
0021 #include <clang/AST/Decl.h>
0022 #include <clang/AST/DeclBase.h>
0023 #include <clang/AST/DeclCXX.h>
0024 #include <clang/AST/Expr.h>
0025 #include <clang/AST/ExprCXX.h>
0026 #include <clang/AST/Stmt.h>
0027 #include <clang/AST/Type.h>
0028 #include <clang/Basic/Diagnostic.h>
0029 #include <clang/Basic/IdentifierTable.h>
0030 #include <clang/Basic/LLVM.h>
0031 #include <clang/Basic/SourceManager.h>
0032 #include <clang/Basic/TokenKinds.h>
0033 #include <clang/Lex/Lexer.h>
0034 #include <clang/Lex/Token.h>
0035 #include <llvm/ADT/StringRef.h>
0036 #include <llvm/Support/Casting.h>
0037 #include <llvm/Support/raw_ostream.h>
0038 
0039 #include <regex>
0040 
0041 namespace clang
0042 {
0043 class MacroInfo;
0044 } // namespace clang
0045 
0046 using namespace clang;
0047 
0048 namespace clazy
0049 {
0050 // Copied from Clang's Expr.cpp and added "|| !DerivedType->isRecordType()" to avoid a crash
0051 const CXXRecordDecl *getBestDynamicClassType(Expr *expr)
0052 {
0053     if (!expr) {
0054         return nullptr;
0055     }
0056 
0057     const Expr *E = expr->getBestDynamicClassTypeExpr();
0058     QualType DerivedType = E->getType();
0059     if (const auto *PTy = DerivedType->getAs<PointerType>()) {
0060         DerivedType = PTy->getPointeeType();
0061     }
0062 
0063     if (DerivedType->isDependentType() || !DerivedType->isRecordType()) {
0064         return nullptr;
0065     }
0066 
0067     const RecordType *Ty = DerivedType->castAs<RecordType>();
0068     Decl *D = Ty->getDecl();
0069     return cast<CXXRecordDecl>(D);
0070 }
0071 }
0072 
0073 enum ConnectFlag {
0074     ConnectFlag_None = 0, // Not a disconnect or connect
0075     ConnectFlag_Connect = 1, // It's a connect
0076     ConnectFlag_Disconnect = 2, // It's a disconnect
0077     ConnectFlag_QTimerSingleShot = 4,
0078     ConnectFlag_OldStyle = 8, // Qt4 style
0079     ConnectFlag_4ArgsDisconnect = 16, // disconnect(const char *signal = 0, const QObject *receiver = 0, const char *method = 0) const
0080     ConnectFlag_3ArgsDisconnect = 32, // disconnect(SIGNAL(foo))
0081     ConnectFlag_2ArgsDisconnect = 64, // disconnect(const QObject *receiver, const char *method = 0) const
0082     ConnectFlag_5ArgsConnect =
0083         128, // connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
0084     ConnectFlag_4ArgsConnect = 256, // connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
0085     ConnectFlag_OldStyleButNonLiteral = 0x200, // connect(foo, SIGNAL(bar()), foo, variableWithSlotName); // here the slot name isn't a literal
0086     ConnectFlag_QStateAddTransition = 0x400,
0087     ConnectFlag_QMenuAddAction = 0x800,
0088     ConnectFlag_QMessageBoxOpen = 0x1000,
0089     ConnectFlag_QSignalSpy = 0x2000,
0090     ConnectFlag_Bogus = 0x4000,
0091 };
0092 
0093 static bool classIsOk(StringRef className)
0094 {
0095     // List of classes we usually use Qt4 syntax
0096     return className != "QDBusInterface";
0097 }
0098 
0099 OldStyleConnect::OldStyleConnect(const std::string &name, ClazyContext *context)
0100     : CheckBase(name, context, Option_CanIgnoreIncludes)
0101 {
0102     enablePreProcessorCallbacks();
0103     context->enableAccessSpecifierManager();
0104 }
0105 
0106 template<typename T>
0107 int OldStyleConnect::classifyConnect(FunctionDecl *connectFunc, T *connectCall) const
0108 {
0109     int classification = ConnectFlag_None;
0110 
0111     const std::string methodName = connectFunc->getQualifiedNameAsString();
0112     if (methodName == "QObject::connect") {
0113         classification |= ConnectFlag_Connect;
0114     } else if (methodName == "QObject::disconnect") {
0115         classification |= ConnectFlag_Disconnect;
0116     } else if (methodName == "QTimer::singleShot") {
0117         classification |= ConnectFlag_QTimerSingleShot;
0118     } else if (methodName == "QState::addTransition") {
0119         classification |= ConnectFlag_QStateAddTransition;
0120     } else if (methodName == "QMenu::addAction" || methodName == "QWidget::addAction") {
0121         classification |= ConnectFlag_QMenuAddAction;
0122     } else if (methodName == "QMessageBox::open") {
0123         classification |= ConnectFlag_QMessageBoxOpen;
0124     } else if (methodName == "QSignalSpy::QSignalSpy") {
0125         classification |= ConnectFlag_QSignalSpy;
0126     }
0127 
0128     if (classification == ConnectFlag_None) {
0129         return classification;
0130     }
0131 
0132     if (clazy::connectHasPMFStyle(connectFunc)) {
0133         return classification;
0134     }
0135     classification |= ConnectFlag_OldStyle;
0136 
0137     const unsigned int numParams = connectFunc->getNumParams();
0138 
0139     if (classification & ConnectFlag_Connect) {
0140         if (numParams == 5) {
0141             classification |= ConnectFlag_5ArgsConnect;
0142         } else if (numParams == 4) {
0143             classification |= ConnectFlag_4ArgsConnect;
0144         } else {
0145             classification |= ConnectFlag_Bogus;
0146         }
0147     } else if (classification & ConnectFlag_Disconnect) {
0148         if (numParams == 4) {
0149             classification |= ConnectFlag_4ArgsDisconnect;
0150         } else if (numParams == 3) {
0151             classification |= ConnectFlag_3ArgsDisconnect;
0152         } else if (numParams == 2) {
0153             classification |= ConnectFlag_2ArgsDisconnect;
0154         } else {
0155             classification |= ConnectFlag_Bogus;
0156         }
0157     }
0158 
0159     if (classification & ConnectFlag_OldStyle) {
0160         // It's old style, but check if all macros are literals
0161         int numLiterals = 0;
0162         for (auto arg : connectCall->arguments()) {
0163             auto argLocation = arg->getBeginLoc();
0164             std::string dummy;
0165             if (isSignalOrSlot(argLocation, dummy)) {
0166                 ++numLiterals;
0167             }
0168         }
0169 
0170         if ((classification & ConnectFlag_QTimerSingleShot) && numLiterals != 1) {
0171             classification |= ConnectFlag_OldStyleButNonLiteral;
0172         } else if (((classification & ConnectFlag_Connect) && numLiterals != 2)) {
0173             classification |= ConnectFlag_OldStyleButNonLiteral;
0174         } else if ((classification & ConnectFlag_4ArgsDisconnect) && numLiterals != 2) {
0175             classification |= ConnectFlag_OldStyleButNonLiteral;
0176         } else if ((classification & ConnectFlag_QStateAddTransition) && numLiterals != 1) {
0177             classification |= ConnectFlag_OldStyleButNonLiteral;
0178         } else if ((classification & ConnectFlag_Disconnect) && numLiterals == 0) {
0179             classification |= ConnectFlag_OldStyleButNonLiteral;
0180         } else if ((classification & ConnectFlag_QMenuAddAction) && numLiterals != 1) {
0181             classification |= ConnectFlag_OldStyleButNonLiteral;
0182         } else if ((classification & ConnectFlag_QMessageBoxOpen) && numLiterals != 1) {
0183             classification |= ConnectFlag_OldStyleButNonLiteral;
0184         } else if ((classification & ConnectFlag_QSignalSpy) && numLiterals != 1) {
0185             classification |= ConnectFlag_OldStyleButNonLiteral;
0186         }
0187     }
0188 
0189     return classification;
0190 }
0191 
0192 bool OldStyleConnect::isQPointer(Expr *expr) const
0193 {
0194     std::vector<CXXMemberCallExpr *> memberCalls;
0195     clazy::getChilds<CXXMemberCallExpr>(expr, memberCalls);
0196 
0197     for (auto *callExpr : memberCalls) {
0198         if (!callExpr->getDirectCallee()) {
0199             continue;
0200         }
0201         auto *method = dyn_cast<CXXMethodDecl>(callExpr->getDirectCallee());
0202         if (!method) {
0203             continue;
0204         }
0205 
0206         // Any better way to detect it's an operator ?
0207         if (clazy::startsWith(method->getNameAsString(), "operator ")) {
0208             return true;
0209         }
0210     }
0211 
0212     return false;
0213 }
0214 
0215 bool OldStyleConnect::isPrivateSlot(const std::string &name) const
0216 {
0217     return clazy::any_of(m_privateSlots, [name](const PrivateSlot &slot) {
0218         return slot.name == name;
0219     });
0220 }
0221 
0222 void OldStyleConnect::VisitStmt(Stmt *s)
0223 {
0224     auto *call = dyn_cast<CallExpr>(s);
0225     auto *ctorExpr = call ? nullptr : dyn_cast<CXXConstructExpr>(s);
0226     if (!call && !ctorExpr) {
0227         return;
0228     }
0229 
0230     if (m_context->lastMethodDecl && m_context->isQtDeveloper() && m_context->lastMethodDecl->getParent()
0231         && clazy::name(m_context->lastMethodDecl->getParent()) == "QObject") { // Don't warn of stuff inside qobject.h
0232         return;
0233     }
0234 
0235     FunctionDecl *function = call ? call->getDirectCallee() : ctorExpr->getConstructor();
0236     if (!function) {
0237         return;
0238     }
0239 
0240     auto *method = dyn_cast<CXXMethodDecl>(function);
0241     if (!method) {
0242         return;
0243     }
0244 
0245     const int classification = call ? classifyConnect(method, call) : classifyConnect(method, ctorExpr);
0246 
0247     if (!(classification & ConnectFlag_OldStyle)) {
0248         return;
0249     }
0250 
0251     if ((classification & ConnectFlag_OldStyleButNonLiteral)) {
0252         return;
0253     }
0254 
0255     if (classification & ConnectFlag_Bogus) {
0256         emitWarning(s->getBeginLoc(), "Internal error");
0257         return;
0258     }
0259 
0260     emitWarning(s->getBeginLoc(), "Old Style Connect", call ? fixits(classification, call) : fixits(classification, ctorExpr));
0261 }
0262 
0263 void OldStyleConnect::addPrivateSlot(const PrivateSlot &slot)
0264 {
0265     m_privateSlots.push_back(slot);
0266 }
0267 
0268 void OldStyleConnect::VisitMacroExpands(const Token &macroNameTok, const SourceRange &range, const MacroInfo *)
0269 {
0270     IdentifierInfo *ii = macroNameTok.getIdentifierInfo();
0271     if (!ii || ii->getName() != "Q_PRIVATE_SLOT") {
0272         return;
0273     }
0274 
0275     auto charRange = Lexer::getAsCharRange(range, sm(), lo());
0276     const std::string text = static_cast<std::string>(Lexer::getSourceText(charRange, sm(), lo()));
0277 
0278     static std::regex rx(R"(Q_PRIVATE_SLOT\s*\((.*),.*\s(.*)\(.*)");
0279     std::smatch match;
0280     if (!regex_match(text, match, rx) || match.size() != 3) {
0281         return;
0282     }
0283 
0284     addPrivateSlot({match[1], match[2]});
0285 }
0286 
0287 // SIGNAL(foo()) -> foo
0288 std::string OldStyleConnect::signalOrSlotNameFromMacro(SourceLocation macroLoc)
0289 {
0290     if (!macroLoc.isMacroID()) {
0291         return "error";
0292     }
0293 
0294     CharSourceRange expansionRange = sm().getImmediateExpansionRange(macroLoc);
0295     SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd());
0296     auto charRange = Lexer::getAsCharRange(range, sm(), lo());
0297     const std::string text = static_cast<std::string>(Lexer::getSourceText(charRange, sm(), lo()));
0298 
0299     static std::regex rx(R"(\s*(SIGNAL|SLOT)\s*\(\s*(.+)\s*\(.*)");
0300 
0301     std::smatch match;
0302     if (regex_match(text, match, rx)) {
0303         if (match.size() == 3) {
0304             return match[2].str();
0305         }
0306         return "error2";
0307 
0308     } else {
0309         return std::string("regexp failed for ") + text;
0310     }
0311 }
0312 
0313 bool OldStyleConnect::isSignalOrSlot(SourceLocation loc, std::string &macroName) const
0314 {
0315     macroName.clear();
0316     if (!loc.isMacroID() || loc.isInvalid()) {
0317         return false;
0318     }
0319 
0320     macroName = static_cast<std::string>(Lexer::getImmediateMacroName(loc, sm(), lo()));
0321     return macroName == "SIGNAL" || macroName == "SLOT";
0322 }
0323 
0324 template<typename T>
0325 std::vector<FixItHint> OldStyleConnect::fixits(int classification, T *callOrCtor)
0326 {
0327     if (!callOrCtor) {
0328         llvm::errs() << "Call is invalid\n";
0329         return {};
0330     }
0331 
0332     const SourceLocation locStart = callOrCtor->getBeginLoc();
0333 
0334     if (classification & ConnectFlag_2ArgsDisconnect) {
0335         // Not implemented yet
0336         std::string msg = "Fix it not implemented for disconnect with 2 args";
0337         queueManualFixitWarning(locStart, msg);
0338         return {};
0339     }
0340 
0341     if (classification & ConnectFlag_3ArgsDisconnect) {
0342         // Not implemented yet
0343         std::string msg = "Fix it not implemented for disconnect with 3 args";
0344         queueManualFixitWarning(locStart, msg);
0345         return {};
0346     }
0347 
0348     if (classification & ConnectFlag_QMessageBoxOpen) {
0349         std::string msg = "Fix it not implemented for QMessageBox::open()";
0350         queueManualFixitWarning(locStart, msg);
0351         return {};
0352     }
0353 
0354     std::vector<FixItHint> fixits;
0355     int macroNum = 0;
0356     std::string implicitCallee;
0357     std::string macroName;
0358     CXXMethodDecl *senderMethod = nullptr;
0359     for (auto arg : callOrCtor->arguments()) {
0360         SourceLocation s = arg->getBeginLoc();
0361         static const CXXRecordDecl *lastRecordDecl = nullptr;
0362         if (isSignalOrSlot(s, macroName)) {
0363             macroNum++;
0364             if (!lastRecordDecl && (classification & ConnectFlag_4ArgsConnect)) {
0365                 // This means it's a connect with implicit receiver
0366                 lastRecordDecl = Utils::recordForMemberCall(dyn_cast<CXXMemberCallExpr>(callOrCtor), implicitCallee);
0367 
0368                 if (macroNum == 1) {
0369                     llvm::errs() << "This first macro shouldn't enter this path";
0370                 }
0371                 if (!lastRecordDecl) {
0372                     std::string msg = "Failed to get class name for implicit receiver";
0373                     queueManualFixitWarning(s, msg);
0374                     return {};
0375                 }
0376             }
0377 
0378             if (!lastRecordDecl) {
0379                 std::string msg = "Failed to get class name for explicit receiver";
0380                 queueManualFixitWarning(s, msg);
0381                 return {};
0382             }
0383 
0384             const std::string methodName = signalOrSlotNameFromMacro(s);
0385 
0386             auto methods = Utils::methodsFromString(lastRecordDecl, methodName);
0387             if (methods.empty()) {
0388                 std::string msg;
0389                 if (isPrivateSlot(methodName)) {
0390                     msg = "Converting Q_PRIVATE_SLOTS not implemented yet\n";
0391                 } else {
0392                     if (m_context->isQtDeveloper() && classIsOk(clazy::name(lastRecordDecl))) {
0393                         // This is OK
0394                         return {};
0395                     }
0396                     msg = "No such method " + methodName + " in class " + lastRecordDecl->getNameAsString();
0397                 }
0398 
0399                 queueManualFixitWarning(s, msg);
0400                 return {};
0401             }
0402             if (methods.size() != 1) {
0403                 std::string msg = std::string("Too many overloads (") + std::to_string(methods.size()) + std::string(") for method ") + methodName
0404                     + " for record " + lastRecordDecl->getNameAsString();
0405                 queueManualFixitWarning(s, msg);
0406                 return {};
0407             } else {
0408                 AccessSpecifierManager *a = m_context->accessSpecifierManager;
0409                 if (!a) {
0410                     return {};
0411                 }
0412                 const bool isSignal = a->qtAccessSpecifierType(methods[0]) == QtAccessSpecifier_Signal;
0413                 if (isSignal && macroName == "SLOT") {
0414                     // The method is actually a signal and the user used SLOT()
0415                     // bail out with the fixing.
0416                     std::string msg = std::string("Can't fix. SLOT macro used but method " + methodName + " is a signal");
0417                     queueManualFixitWarning(s, msg);
0418                     return {};
0419                 }
0420             }
0421 
0422             auto *methodDecl = methods[0];
0423             if (methodDecl->isStatic()) {
0424                 return {};
0425             }
0426 
0427             if (macroNum == 1) {
0428                 // Save the number of parameters of the signal. The slot should not have more arguments.
0429                 senderMethod = methodDecl;
0430             } else if (macroNum == 2) {
0431                 const unsigned int numReceiverParams = methodDecl->getNumParams();
0432                 if (numReceiverParams > senderMethod->getNumParams()) {
0433                     std::string msg = std::string("Receiver has more parameters (") + std::to_string(methodDecl->getNumParams()) + ") than signal ("
0434                         + std::to_string(senderMethod->getNumParams()) + ')';
0435                     queueManualFixitWarning(s, msg);
0436                     return {};
0437                 }
0438 
0439                 for (unsigned int i = 0; i < numReceiverParams; ++i) {
0440                     ParmVarDecl *receiverParm = methodDecl->getParamDecl(i);
0441                     ParmVarDecl *senderParm = senderMethod->getParamDecl(i);
0442                     if (!clazy::isConvertibleTo(senderParm->getType().getTypePtr(), receiverParm->getType().getTypePtrOrNull())) {
0443                         std::string msg("Sender's parameters are incompatible with the receiver's");
0444                         queueManualFixitWarning(s, msg);
0445                         return {};
0446                     }
0447                 }
0448             }
0449 
0450             if ((classification & ConnectFlag_QTimerSingleShot) && methodDecl->getNumParams() > 0) {
0451                 std::string msg = "(QTimer) Fixit not implemented for slot with arguments, use a lambda";
0452                 queueManualFixitWarning(s, msg);
0453                 return {};
0454             }
0455 
0456             if ((classification & ConnectFlag_QMenuAddAction) && methodDecl->getNumParams() > 0) {
0457                 std::string msg = "(QMenu) Fixit not implemented for slot with arguments, use a lambda";
0458                 queueManualFixitWarning(s, msg);
0459                 return {};
0460             }
0461 
0462             DeclContext *context = m_context->lastDecl->getDeclContext();
0463 
0464             bool isSpecialProtectedCase = false;
0465             if (!clazy::canTakeAddressOf(methodDecl, context, /*by-ref*/ isSpecialProtectedCase)) {
0466                 std::string msg = "Can't fix " + clazy::accessString(methodDecl->getAccess()) + ' ' + macroName + ' ' + methodDecl->getQualifiedNameAsString();
0467                 queueManualFixitWarning(s, msg);
0468                 return {};
0469             }
0470 
0471             std::string qualifiedName;
0472             auto *contextRecord = clazy::firstContextOfType<CXXRecordDecl>(m_context->lastDecl->getDeclContext());
0473             const bool isInInclude = sm().getMainFileID() != sm().getFileID(locStart);
0474 
0475             if (isSpecialProtectedCase && contextRecord) {
0476                 // We're inside a derived class trying to take address of a protected base member, must use &Derived::method instead of &Base::method.
0477                 qualifiedName = contextRecord->getNameAsString() + "::" + methodDecl->getNameAsString();
0478             } else {
0479                 qualifiedName = clazy::getMostNeededQualifiedName(sm(), methodDecl, context, locStart, !isInInclude); // (In includes ignore using directives)
0480             }
0481 
0482             CharSourceRange expansionRange = sm().getImmediateExpansionRange(s);
0483             SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd());
0484 
0485             const std::string functionPointer = '&' + qualifiedName;
0486             std::string replacement = functionPointer;
0487 
0488             if ((classification & ConnectFlag_4ArgsConnect) && macroNum == 2) {
0489                 replacement = implicitCallee + ", " + replacement;
0490             }
0491 
0492             fixits.push_back(FixItHint::CreateReplacement(range, replacement));
0493             lastRecordDecl = nullptr;
0494         } else {
0495             Expr *expr = arg;
0496             const auto *const record = clazy::getBestDynamicClassType(expr);
0497             if (record) {
0498                 lastRecordDecl = record;
0499                 if (isQPointer(expr)) {
0500                     auto endLoc = clazy::locForNextToken(&m_astContext, arg->getBeginLoc(), tok::comma);
0501                     if (endLoc.isValid()) {
0502                         fixits.push_back(FixItHint::CreateInsertion(endLoc, ".data()"));
0503                     } else {
0504                         queueManualFixitWarning(s, "Can't fix this QPointer case");
0505                         return {};
0506                     }
0507                 }
0508             }
0509         }
0510     }
0511 
0512     return fixits;
0513 }