diff --git a/src/checks/level2/old-style-connect.cpp b/src/checks/level2/old-style-connect.cpp index ebc4acc..991939b 100644 --- a/src/checks/level2/old-style-connect.cpp +++ b/src/checks/level2/old-style-connect.cpp @@ -1,457 +1,478 @@ /* This file is part of the clazy static checker. Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Author: SĂ©rgio Martins Copyright (C) 2015 Sergio Martins This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "old-style-connect.h" #include "Utils.h" #include "StringUtils.h" #include "FixItUtils.h" #include "ContextUtils.h" #include "QtUtils.h" #include "ClazyContext.h" #include "AccessSpecifierManager.h" #include #include #include #include #include #include using namespace clang; using namespace std; +namespace clazy { +// Copied from Clang's Expr.cpp and added "|| !DerivedType->isRecordType()" to avoid a crash +const CXXRecordDecl *getBestDynamicClassType(Expr *expr) +{ + if (!expr) + return nullptr; + + const Expr *E = expr->getBestDynamicClassTypeExpr(); + QualType DerivedType = E->getType(); + if (const PointerType *PTy = DerivedType->getAs()) + DerivedType = PTy->getPointeeType(); + + if (DerivedType->isDependentType() || !DerivedType->isRecordType()) + return nullptr; + + const RecordType *Ty = DerivedType->castAs(); + Decl *D = Ty->getDecl(); + return cast(D); +} +} + enum ConnectFlag { ConnectFlag_None = 0, // Not a disconnect or connect ConnectFlag_Connect = 1, // It's a connect ConnectFlag_Disconnect = 2, // It's a disconnect ConnectFlag_QTimerSingleShot = 4, ConnectFlag_OldStyle = 8, // Qt4 style ConnectFlag_4ArgsDisconnect = 16, // disconnect(const char *signal = 0, const QObject *receiver = 0, const char *method = 0) const ConnectFlag_3ArgsDisconnect = 32, // disconnect(SIGNAL(foo)) ConnectFlag_2ArgsDisconnect = 64, //disconnect(const QObject *receiver, const char *method = 0) const ConnectFlag_5ArgsConnect = 128, // connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ConnectFlag_4ArgsConnect = 256, // connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) ConnectFlag_OldStyleButNonLiteral = 0x200, // connect(foo, SIGNAL(bar()), foo, variableWithSlotName); // here the slot name isn't a literal ConnectFlag_QStateAddTransition = 0x400, ConnectFlag_QMenuAddAction = 0x800, ConnectFlag_QMessageBoxOpen = 0x1000, ConnectFlag_Bogus = 0x2000 }; static bool classIsOk(StringRef className) { // List of classes we usually use Qt4 syntax return className != "QDBusInterface"; } OldStyleConnect::OldStyleConnect(const std::string &name, ClazyContext *context) : CheckBase(name, context, Option_CanIgnoreIncludes) { enablePreProcessorCallbacks(); context->enableAccessSpecifierManager(); } int OldStyleConnect::classifyConnect(FunctionDecl *connectFunc, CallExpr *connectCall) { int classification = ConnectFlag_None; const string methodName = connectFunc->getQualifiedNameAsString(); if (methodName == "QObject::connect") classification |= ConnectFlag_Connect; else if (methodName == "QObject::disconnect") classification |= ConnectFlag_Disconnect; else if (methodName == "QTimer::singleShot") classification |= ConnectFlag_QTimerSingleShot; else if (methodName == "QState::addTransition") classification |= ConnectFlag_QStateAddTransition; else if (methodName == "QMenu::addAction") classification |= ConnectFlag_QMenuAddAction; else if (methodName == "QMessageBox::open") classification |= ConnectFlag_QMessageBoxOpen; if (classification == ConnectFlag_None) return classification; if (clazy::connectHasPMFStyle(connectFunc)) return classification; else classification |= ConnectFlag_OldStyle; const int numParams = connectFunc->getNumParams(); if (classification & ConnectFlag_Connect) { if (numParams == 5) { classification |= ConnectFlag_5ArgsConnect; } else if (numParams == 4) { classification |= ConnectFlag_4ArgsConnect; } else { classification |= ConnectFlag_Bogus; } } else if (classification & ConnectFlag_Disconnect) { if (numParams == 4) { classification |= ConnectFlag_4ArgsDisconnect; } else if (numParams == 3) { classification |= ConnectFlag_3ArgsDisconnect; } else if (numParams == 2) { classification |= ConnectFlag_2ArgsDisconnect; } else { classification |= ConnectFlag_Bogus; } } if (classification & ConnectFlag_OldStyle) { // It's old style, but check if all macros are literals int numLiterals = 0; for (auto arg : connectCall->arguments()) { auto argLocation = getLocStart(arg); string dummy; if (isSignalOrSlot(argLocation, dummy)) ++numLiterals; } if ((classification & ConnectFlag_QTimerSingleShot) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if (((classification & ConnectFlag_Connect) && numLiterals != 2)) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_4ArgsDisconnect) && numLiterals != 2) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QStateAddTransition) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_Disconnect) && numLiterals == 0) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QMenuAddAction) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } else if ((classification & ConnectFlag_QMessageBoxOpen) && numLiterals != 1) { classification |= ConnectFlag_OldStyleButNonLiteral; } } return classification; } bool OldStyleConnect::isQPointer(Expr *expr) const { vector memberCalls; clazy::getChilds(expr, memberCalls); for (auto callExpr : memberCalls) { if (!callExpr->getDirectCallee()) continue; auto method = dyn_cast(callExpr->getDirectCallee()); if (!method) continue; // Any better way to detect it's an operator ? if (clazy::startsWith(method->getNameAsString(), "operator ")) return true; } return false; } bool OldStyleConnect::isPrivateSlot(const string &name) const { return clazy::any_of(m_privateSlots, [name](const PrivateSlot &slot) { return slot.name == name; }); } void OldStyleConnect::VisitStmt(Stmt *s) { auto call = dyn_cast(s); if (!call) return; if (m_context->lastMethodDecl && m_context->isQtDeveloper() && m_context->lastMethodDecl->getParent() && clazy::name(m_context->lastMethodDecl->getParent()) == "QObject") // Don't warn of stuff inside qobject.h return; FunctionDecl *function = call->getDirectCallee(); if (!function) return; auto method = dyn_cast(function); if (!method) return; const int classification = classifyConnect(method, call); if (!(classification & ConnectFlag_OldStyle)) return; if ((classification & ConnectFlag_OldStyleButNonLiteral)) return; if (classification & ConnectFlag_Bogus) { emitWarning(getLocStart(s), "Internal error"); return; } emitWarning(getLocStart(s), "Old Style Connect", fixits(classification, call)); } void OldStyleConnect::addPrivateSlot(const PrivateSlot &slot) { m_privateSlots.push_back(slot); } void OldStyleConnect::VisitMacroExpands(const Token ¯oNameTok, const SourceRange &range, const MacroInfo *) { IdentifierInfo *ii = macroNameTok.getIdentifierInfo(); if (!ii || ii->getName() != "Q_PRIVATE_SLOT") return; auto charRange = Lexer::getAsCharRange(range, sm(), lo()); const string text = Lexer::getSourceText(charRange, sm(), lo()); static regex rx(R"(Q_PRIVATE_SLOT\s*\((.*)\s*,\s*.*\s+(.*)\(.*)"); smatch match; if (!regex_match(text, match, rx) || match.size() != 3) return; addPrivateSlot({match[1], match[2]}); } // SIGNAL(foo()) -> foo string OldStyleConnect::signalOrSlotNameFromMacro(SourceLocation macroLoc) { if (!macroLoc.isMacroID()) return "error"; CharSourceRange expansionRange = getImmediateExpansionRange(macroLoc, sm()); SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd()); auto charRange = Lexer::getAsCharRange(range, sm(), lo()); const string text = Lexer::getSourceText(charRange, sm(), lo()); static regex rx(R"(\s*(SIGNAL|SLOT)\s*\(\s*(.+)\s*\(.*)"); smatch match; if (regex_match(text, match, rx)) { if (match.size() == 3) { return match[2].str(); } else { return "error2"; } } else { return string("regexp failed for ") + text; } } bool OldStyleConnect::isSignalOrSlot(SourceLocation loc, string ¯oName) const { macroName.clear(); if (!loc.isMacroID() || loc.isInvalid()) return false; macroName = Lexer::getImmediateMacroName(loc, sm(), lo()); return macroName == "SIGNAL" || macroName == "SLOT"; } vector OldStyleConnect::fixits(int classification, CallExpr *call) { if (!isFixitEnabled()) return {}; if (!call) { llvm::errs() << "Call is invalid\n"; return {}; } if (classification & ConnectFlag_2ArgsDisconnect) { // Not implemented yet string msg = "Fix it not implemented for disconnect with 2 args"; queueManualFixitWarning(getLocStart(call), msg); return {}; } if (classification & ConnectFlag_3ArgsDisconnect) { // Not implemented yet string msg = "Fix it not implemented for disconnect with 3 args"; queueManualFixitWarning(getLocStart(call), msg); return {}; } if (classification & ConnectFlag_QMessageBoxOpen) { string msg = "Fix it not implemented for QMessageBox::open()"; queueManualFixitWarning(getLocStart(call), msg); return {}; } vector fixits; int macroNum = 0; string implicitCallee; string macroName; CXXMethodDecl *senderMethod = nullptr; for (auto arg : call->arguments()) { SourceLocation s = getLocStart(arg); static const CXXRecordDecl *lastRecordDecl = nullptr; if (isSignalOrSlot(s, macroName)) { macroNum++; if (!lastRecordDecl && (classification & ConnectFlag_4ArgsConnect)) { // This means it's a connect with implicit receiver lastRecordDecl = Utils::recordForMemberCall(dyn_cast(call), implicitCallee); if (macroNum == 1) llvm::errs() << "This first macro shouldn't enter this path"; if (!lastRecordDecl) { string msg = "Failed to get class name for implicit receiver"; queueManualFixitWarning(s, msg); return {}; } } if (!lastRecordDecl) { string msg = "Failed to get class name for explicit receiver"; queueManualFixitWarning(s, msg); return {}; } const string methodName = signalOrSlotNameFromMacro(s); auto methods = Utils::methodsFromString(lastRecordDecl, methodName); if (methods.empty()) { string msg; if (isPrivateSlot(methodName)) { msg = "Converting Q_PRIVATE_SLOTS not implemented yet\n"; } else { if (m_context->isQtDeveloper() && classIsOk(clazy::name(lastRecordDecl))) { // This is OK return {}; } else { msg = "No such method " + methodName + " in class " + lastRecordDecl->getNameAsString(); } } queueManualFixitWarning(s, msg); return {}; } else if (methods.size() != 1) { string msg = string("Too many overloads (") + to_string(methods.size()) + string(") for method ") + methodName + " for record " + lastRecordDecl->getNameAsString(); queueManualFixitWarning(s, msg); return {}; } else { AccessSpecifierManager *a = m_context->accessSpecifierManager; if (!a) return {}; const bool isSignal = a->qtAccessSpecifierType(methods[0]) == QtAccessSpecifier_Signal; if (isSignal && macroName == "SLOT") { // The method is actually a signal and the user used SLOT() // bail out with the fixing. string msg = string("Can't fix. SLOT macro used but method " + methodName + " is a signal"); queueManualFixitWarning(s, msg); return {}; } } auto methodDecl = methods[0]; if (methodDecl->isStatic()) return {}; if (macroNum == 1) { // Save the number of parameters of the signal. The slot should not have more arguments. senderMethod = methodDecl; } else if (macroNum == 2) { const unsigned int numReceiverParams = methodDecl->getNumParams(); if (numReceiverParams > senderMethod->getNumParams()) { string msg = string("Receiver has more parameters (") + to_string(methodDecl->getNumParams()) + ") than signal (" + to_string(senderMethod->getNumParams()) + ')'; queueManualFixitWarning(s, msg); return {}; } for (unsigned int i = 0; i < numReceiverParams; ++i) { ParmVarDecl *receiverParm = methodDecl->getParamDecl(i); ParmVarDecl *senderParm = senderMethod->getParamDecl(i); if (!clazy::isConvertibleTo(senderParm->getType().getTypePtr(), receiverParm->getType().getTypePtrOrNull())) { string msg = string("Sender's parameters are incompatible with the receiver's"); queueManualFixitWarning(s, msg); return {}; } } } if ((classification & ConnectFlag_QTimerSingleShot) && methodDecl->getNumParams() > 0) { string msg = "(QTimer) Fixit not implemented for slot with arguments, use a lambda"; queueManualFixitWarning(s, msg); return {}; } if ((classification & ConnectFlag_QMenuAddAction) && methodDecl->getNumParams() > 0) { string msg = "(QMenu) Fixit not implemented for slot with arguments, use a lambda"; queueManualFixitWarning(s, msg); return {}; } DeclContext *context = m_context->lastDecl->getDeclContext(); bool isSpecialProtectedCase = false; if (!clazy::canTakeAddressOf(methodDecl, context, /*by-ref*/isSpecialProtectedCase)) { string msg = "Can't fix " + clazy::accessString(methodDecl->getAccess()) + ' ' + macroName + ' ' + methodDecl->getQualifiedNameAsString(); queueManualFixitWarning(s, msg); return {}; } string qualifiedName; auto contextRecord = clazy::firstContextOfType(m_context->lastDecl->getDeclContext()); const bool isInInclude = sm().getMainFileID() != sm().getFileID(getLocStart(call)); if (isSpecialProtectedCase && contextRecord) { // We're inside a derived class trying to take address of a protected base member, must use &Derived::method instead of &Base::method. qualifiedName = contextRecord->getNameAsString() + "::" + methodDecl->getNameAsString() ; } else { qualifiedName = clazy::getMostNeededQualifiedName(sm(), methodDecl, context, getLocStart(call), !isInInclude); // (In includes ignore using directives) } CharSourceRange expansionRange = getImmediateExpansionRange(s, sm()); SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd()); const string functionPointer = '&' + qualifiedName; string replacement = functionPointer; if ((classification & ConnectFlag_4ArgsConnect) && macroNum == 2) replacement = implicitCallee + ", " + replacement; fixits.push_back(FixItHint::CreateReplacement(range, replacement)); lastRecordDecl = nullptr; } else { Expr *expr = arg; - const auto record = expr ? expr->getBestDynamicClassType() : nullptr; + const auto record = clazy::getBestDynamicClassType(expr); if (record) { lastRecordDecl = record; if (isQPointer(expr)) { auto endLoc = clazy::locForNextToken(&m_astContext, getLocStart(arg), tok::comma); if (endLoc.isValid()) { fixits.push_back(FixItHint::CreateInsertion(endLoc, ".data()")); } else { queueManualFixitWarning(s, "Can't fix this QPointer case"); return {}; } } } } } return fixits; }