aboutsummaryrefslogtreecommitdiffstats
path: root/src/checks/level2/old-style-connect.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/checks/level2/old-style-connect.cpp')
-rw-r--r--src/checks/level2/old-style-connect.cpp457
1 files changed, 457 insertions, 0 deletions
diff --git a/src/checks/level2/old-style-connect.cpp b/src/checks/level2/old-style-connect.cpp
new file mode 100644
index 00000000..ebc4acca
--- /dev/null
+++ b/src/checks/level2/old-style-connect.cpp
@@ -0,0 +1,457 @@
+/*
+ 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 <sergio.martins@kdab.com>
+
+ Copyright (C) 2015 Sergio Martins <smartins@kde.org>
+
+ 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 <clang/AST/Decl.h>
+#include <clang/AST/DeclCXX.h>
+#include <clang/AST/AST.h>
+#include <clang/AST/DeclTemplate.h>
+#include <clang/Lex/Lexer.h>
+
+#include <regex>
+
+using namespace clang;
+using namespace std;
+
+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<CXXMemberCallExpr*> memberCalls;
+ clazy::getChilds<CXXMemberCallExpr>(expr, memberCalls);
+
+ for (auto callExpr : memberCalls) {
+ if (!callExpr->getDirectCallee())
+ continue;
+ auto method = dyn_cast<CXXMethodDecl>(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<CallExpr>(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<CXXMethodDecl>(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 &macroNameTok, 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 &macroName) const
+{
+ macroName.clear();
+ if (!loc.isMacroID() || loc.isInvalid())
+ return false;
+
+ macroName = Lexer::getImmediateMacroName(loc, sm(), lo());
+ return macroName == "SIGNAL" || macroName == "SLOT";
+}
+
+vector<FixItHint> 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<FixItHint> 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<CXXMemberCallExpr>(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<CXXRecordDecl>(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;
+ 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;
+}