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 ¯oNameTok, 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 ¯oName) 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 }