1 /*
2 This file is part of the clazy static checker.
3
4 Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
5 Author: Sérgio Martins <sergio.martins@kdab.com>
6
7 Copyright (C) 2015 Sergio Martins <smartins@kde.org>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23 */
24
25 #include "old-style-connect.h"
26 #include "Utils.h"
27 #include "StringUtils.h"
28 #include "FixItUtils.h"
29 #include "ContextUtils.h"
30 #include "QtUtils.h"
31 #include "ClazyContext.h"
32 #include "AccessSpecifierManager.h"
33 #include "HierarchyUtils.h"
34 #include "SourceCompatibilityHelpers.h"
35 #include "clazy_stl.h"
36
37 #include <clang/AST/Decl.h>
38 #include <clang/AST/DeclCXX.h>
39 #include <clang/Lex/Lexer.h>
40 #include <clang/AST/DeclBase.h>
41 #include <clang/AST/Expr.h>
42 #include <clang/AST/ExprCXX.h>
43 #include <clang/AST/Stmt.h>
44 #include <clang/AST/Type.h>
45 #include <clang/Basic/Diagnostic.h>
46 #include <clang/Basic/IdentifierTable.h>
47 #include <clang/Basic/LLVM.h>
48 #include <clang/Basic/SourceManager.h>
49 #include <clang/Basic/TokenKinds.h>
50 #include <clang/Lex/Token.h>
51 #include <llvm/ADT/StringRef.h>
52 #include <llvm/Support/Casting.h>
53 #include <llvm/Support/raw_ostream.h>
54
55 #include <regex>
56
57 namespace clang {
58 class MacroInfo;
59 } // namespace clang
60
61 using namespace clang;
62 using namespace std;
63
64 namespace clazy {
65 // Copied from Clang's Expr.cpp and added "|| !DerivedType->isRecordType()" to avoid a crash
getBestDynamicClassType(Expr * expr)66 const CXXRecordDecl *getBestDynamicClassType(Expr *expr)
67 {
68 if (!expr)
69 return nullptr;
70
71 const Expr *E = expr->getBestDynamicClassTypeExpr();
72 QualType DerivedType = E->getType();
73 if (const PointerType *PTy = DerivedType->getAs<PointerType>())
74 DerivedType = PTy->getPointeeType();
75
76 if (DerivedType->isDependentType() || !DerivedType->isRecordType())
77 return nullptr;
78
79 const RecordType *Ty = DerivedType->castAs<RecordType>();
80 Decl *D = Ty->getDecl();
81 return cast<CXXRecordDecl>(D);
82 }
83 }
84
85 enum ConnectFlag {
86 ConnectFlag_None = 0, // Not a disconnect or connect
87 ConnectFlag_Connect = 1, // It's a connect
88 ConnectFlag_Disconnect = 2, // It's a disconnect
89 ConnectFlag_QTimerSingleShot = 4,
90 ConnectFlag_OldStyle = 8, // Qt4 style
91 ConnectFlag_4ArgsDisconnect = 16, // disconnect(const char *signal = 0, const QObject *receiver = 0, const char *method = 0) const
92 ConnectFlag_3ArgsDisconnect = 32, // disconnect(SIGNAL(foo))
93 ConnectFlag_2ArgsDisconnect = 64, //disconnect(const QObject *receiver, const char *method = 0) const
94 ConnectFlag_5ArgsConnect = 128, // connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
95 ConnectFlag_4ArgsConnect = 256, // connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
96 ConnectFlag_OldStyleButNonLiteral = 0x200, // connect(foo, SIGNAL(bar()), foo, variableWithSlotName); // here the slot name isn't a literal
97 ConnectFlag_QStateAddTransition = 0x400,
98 ConnectFlag_QMenuAddAction = 0x800,
99 ConnectFlag_QMessageBoxOpen = 0x1000,
100 ConnectFlag_Bogus = 0x2000
101 };
102
classIsOk(StringRef className)103 static bool classIsOk(StringRef className)
104 {
105 // List of classes we usually use Qt4 syntax
106 return className != "QDBusInterface";
107 }
108
OldStyleConnect(const std::string & name,ClazyContext * context)109 OldStyleConnect::OldStyleConnect(const std::string &name, ClazyContext *context)
110 : CheckBase(name, context, Option_CanIgnoreIncludes)
111 {
112 enablePreProcessorCallbacks();
113 context->enableAccessSpecifierManager();
114 }
115
classifyConnect(FunctionDecl * connectFunc,CallExpr * connectCall)116 int OldStyleConnect::classifyConnect(FunctionDecl *connectFunc, CallExpr *connectCall)
117 {
118 int classification = ConnectFlag_None;
119
120 const string methodName = connectFunc->getQualifiedNameAsString();
121 if (methodName == "QObject::connect")
122 classification |= ConnectFlag_Connect;
123 else if (methodName == "QObject::disconnect")
124 classification |= ConnectFlag_Disconnect;
125 else if (methodName == "QTimer::singleShot")
126 classification |= ConnectFlag_QTimerSingleShot;
127 else if (methodName == "QState::addTransition")
128 classification |= ConnectFlag_QStateAddTransition;
129 else if (methodName == "QMenu::addAction")
130 classification |= ConnectFlag_QMenuAddAction;
131 else if (methodName == "QMessageBox::open")
132 classification |= ConnectFlag_QMessageBoxOpen;
133
134 if (classification == ConnectFlag_None)
135 return classification;
136
137 if (clazy::connectHasPMFStyle(connectFunc))
138 return classification;
139 else
140 classification |= ConnectFlag_OldStyle;
141
142 const int numParams = connectFunc->getNumParams();
143
144 if (classification & ConnectFlag_Connect) {
145 if (numParams == 5) {
146 classification |= ConnectFlag_5ArgsConnect;
147 } else if (numParams == 4) {
148 classification |= ConnectFlag_4ArgsConnect;
149 } else {
150 classification |= ConnectFlag_Bogus;
151 }
152 } else if (classification & ConnectFlag_Disconnect) {
153 if (numParams == 4) {
154 classification |= ConnectFlag_4ArgsDisconnect;
155 } else if (numParams == 3) {
156 classification |= ConnectFlag_3ArgsDisconnect;
157 } else if (numParams == 2) {
158 classification |= ConnectFlag_2ArgsDisconnect;
159 } else {
160 classification |= ConnectFlag_Bogus;
161 }
162 }
163
164 if (classification & ConnectFlag_OldStyle) {
165 // It's old style, but check if all macros are literals
166 int numLiterals = 0;
167 for (auto arg : connectCall->arguments()) {
168 auto argLocation = clazy::getLocStart(arg);
169 string dummy;
170 if (isSignalOrSlot(argLocation, dummy))
171 ++numLiterals;
172 }
173
174 if ((classification & ConnectFlag_QTimerSingleShot) && numLiterals != 1) {
175 classification |= ConnectFlag_OldStyleButNonLiteral;
176 } else if (((classification & ConnectFlag_Connect) && numLiterals != 2)) {
177 classification |= ConnectFlag_OldStyleButNonLiteral;
178 } else if ((classification & ConnectFlag_4ArgsDisconnect) && numLiterals != 2) {
179 classification |= ConnectFlag_OldStyleButNonLiteral;
180 } else if ((classification & ConnectFlag_QStateAddTransition) && numLiterals != 1) {
181 classification |= ConnectFlag_OldStyleButNonLiteral;
182 } else if ((classification & ConnectFlag_Disconnect) && numLiterals == 0) {
183 classification |= ConnectFlag_OldStyleButNonLiteral;
184 } else if ((classification & ConnectFlag_QMenuAddAction) && numLiterals != 1) {
185 classification |= ConnectFlag_OldStyleButNonLiteral;
186 } else if ((classification & ConnectFlag_QMessageBoxOpen) && numLiterals != 1) {
187 classification |= ConnectFlag_OldStyleButNonLiteral;
188 }
189 }
190
191 return classification;
192 }
193
isQPointer(Expr * expr) const194 bool OldStyleConnect::isQPointer(Expr *expr) const
195 {
196 vector<CXXMemberCallExpr*> memberCalls;
197 clazy::getChilds<CXXMemberCallExpr>(expr, memberCalls);
198
199 for (auto callExpr : memberCalls) {
200 if (!callExpr->getDirectCallee())
201 continue;
202 auto method = dyn_cast<CXXMethodDecl>(callExpr->getDirectCallee());
203 if (!method)
204 continue;
205
206 // Any better way to detect it's an operator ?
207 if (clazy::startsWith(method->getNameAsString(), "operator "))
208 return true;
209 }
210
211 return false;
212 }
213
isPrivateSlot(const string & name) const214 bool OldStyleConnect::isPrivateSlot(const string &name) const
215 {
216 return clazy::any_of(m_privateSlots, [name](const PrivateSlot &slot) {
217 return slot.name == name;
218 });
219 }
220
VisitStmt(Stmt * s)221 void OldStyleConnect::VisitStmt(Stmt *s)
222 {
223 auto call = dyn_cast<CallExpr>(s);
224 if (!call)
225 return;
226
227 if (m_context->lastMethodDecl && m_context->isQtDeveloper() && m_context->lastMethodDecl->getParent() &&
228 clazy::name(m_context->lastMethodDecl->getParent()) == "QObject") // Don't warn of stuff inside qobject.h
229 return;
230
231 FunctionDecl *function = call->getDirectCallee();
232 if (!function)
233 return;
234
235 auto method = dyn_cast<CXXMethodDecl>(function);
236 if (!method)
237 return;
238
239 const int classification = classifyConnect(method, call);
240 if (!(classification & ConnectFlag_OldStyle))
241 return;
242
243 if ((classification & ConnectFlag_OldStyleButNonLiteral))
244 return;
245
246 if (classification & ConnectFlag_Bogus) {
247 emitWarning(clazy::getLocStart(s), "Internal error");
248 return;
249 }
250
251 emitWarning(clazy::getLocStart(s), "Old Style Connect", fixits(classification, call));
252 }
253
addPrivateSlot(const PrivateSlot & slot)254 void OldStyleConnect::addPrivateSlot(const PrivateSlot &slot)
255 {
256 m_privateSlots.push_back(slot);
257 }
258
VisitMacroExpands(const Token & macroNameTok,const SourceRange & range,const MacroInfo *)259 void OldStyleConnect::VisitMacroExpands(const Token ¯oNameTok, const SourceRange &range, const MacroInfo *)
260 {
261 IdentifierInfo *ii = macroNameTok.getIdentifierInfo();
262 if (!ii || ii->getName() != "Q_PRIVATE_SLOT")
263 return;
264
265 auto charRange = Lexer::getAsCharRange(range, sm(), lo());
266 const string text = Lexer::getSourceText(charRange, sm(), lo());
267
268 static regex rx(R"(Q_PRIVATE_SLOT\s*\((.*)\s*,\s*.*\s+(.*)\(.*)");
269 smatch match;
270 if (!regex_match(text, match, rx) || match.size() != 3)
271 return;
272
273 addPrivateSlot({match[1], match[2]});
274 }
275
276 // SIGNAL(foo()) -> foo
signalOrSlotNameFromMacro(SourceLocation macroLoc)277 string OldStyleConnect::signalOrSlotNameFromMacro(SourceLocation macroLoc)
278 {
279 if (!macroLoc.isMacroID())
280 return "error";
281
282 CharSourceRange expansionRange = clazy::getImmediateExpansionRange(macroLoc, sm());
283 SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd());
284 auto charRange = Lexer::getAsCharRange(range, sm(), lo());
285 const string text = Lexer::getSourceText(charRange, sm(), lo());
286
287 static regex rx(R"(\s*(SIGNAL|SLOT)\s*\(\s*(.+)\s*\(.*)");
288
289 smatch match;
290 if (regex_match(text, match, rx)) {
291 if (match.size() == 3) {
292 return match[2].str();
293 } else {
294 return "error2";
295 }
296 } else {
297 return string("regexp failed for ") + text;
298 }
299 }
300
isSignalOrSlot(SourceLocation loc,string & macroName) const301 bool OldStyleConnect::isSignalOrSlot(SourceLocation loc, string ¯oName) const
302 {
303 macroName.clear();
304 if (!loc.isMacroID() || loc.isInvalid())
305 return false;
306
307 macroName = Lexer::getImmediateMacroName(loc, sm(), lo());
308 return macroName == "SIGNAL" || macroName == "SLOT";
309 }
310
fixits(int classification,CallExpr * call)311 vector<FixItHint> OldStyleConnect::fixits(int classification, CallExpr *call)
312 {
313 if (!isFixitEnabled())
314 return {};
315
316 if (!call) {
317 llvm::errs() << "Call is invalid\n";
318 return {};
319 }
320
321 if (classification & ConnectFlag_2ArgsDisconnect) {
322 // Not implemented yet
323 string msg = "Fix it not implemented for disconnect with 2 args";
324 queueManualFixitWarning(clazy::getLocStart(call), msg);
325 return {};
326 }
327
328 if (classification & ConnectFlag_3ArgsDisconnect) {
329 // Not implemented yet
330 string msg = "Fix it not implemented for disconnect with 3 args";
331 queueManualFixitWarning(clazy::getLocStart(call), msg);
332 return {};
333 }
334
335 if (classification & ConnectFlag_QMessageBoxOpen) {
336 string msg = "Fix it not implemented for QMessageBox::open()";
337 queueManualFixitWarning(clazy::getLocStart(call), msg);
338 return {};
339 }
340
341 vector<FixItHint> fixits;
342 int macroNum = 0;
343 string implicitCallee;
344 string macroName;
345 CXXMethodDecl *senderMethod = nullptr;
346 for (auto arg : call->arguments()) {
347 SourceLocation s = clazy::getLocStart(arg);
348 static const CXXRecordDecl *lastRecordDecl = nullptr;
349 if (isSignalOrSlot(s, macroName)) {
350 macroNum++;
351 if (!lastRecordDecl && (classification & ConnectFlag_4ArgsConnect)) {
352 // This means it's a connect with implicit receiver
353 lastRecordDecl = Utils::recordForMemberCall(dyn_cast<CXXMemberCallExpr>(call), implicitCallee);
354
355 if (macroNum == 1)
356 llvm::errs() << "This first macro shouldn't enter this path";
357 if (!lastRecordDecl) {
358 string msg = "Failed to get class name for implicit receiver";
359 queueManualFixitWarning(s, msg);
360 return {};
361 }
362 }
363
364 if (!lastRecordDecl) {
365 string msg = "Failed to get class name for explicit receiver";
366 queueManualFixitWarning(s, msg);
367 return {};
368 }
369
370 const string methodName = signalOrSlotNameFromMacro(s);
371
372 auto methods = Utils::methodsFromString(lastRecordDecl, methodName);
373 if (methods.empty()) {
374 string msg;
375 if (isPrivateSlot(methodName)) {
376 msg = "Converting Q_PRIVATE_SLOTS not implemented yet\n";
377 } else {
378 if (m_context->isQtDeveloper() && classIsOk(clazy::name(lastRecordDecl))) {
379 // This is OK
380 return {};
381 } else {
382 msg = "No such method " + methodName + " in class " + lastRecordDecl->getNameAsString();
383 }
384 }
385
386 queueManualFixitWarning(s, msg);
387 return {};
388 } else if (methods.size() != 1) {
389 string msg = string("Too many overloads (") + to_string(methods.size()) + string(") for method ")
390 + methodName + " for record " + lastRecordDecl->getNameAsString();
391 queueManualFixitWarning(s, msg);
392 return {};
393 } else {
394 AccessSpecifierManager *a = m_context->accessSpecifierManager;
395 if (!a)
396 return {};
397
398 const bool isSignal = a->qtAccessSpecifierType(methods[0]) == QtAccessSpecifier_Signal;
399 if (isSignal && macroName == "SLOT") {
400 // The method is actually a signal and the user used SLOT()
401 // bail out with the fixing.
402 string msg = string("Can't fix. SLOT macro used but method " + methodName + " is a signal");
403 queueManualFixitWarning(s, msg);
404 return {};
405 }
406 }
407
408 auto methodDecl = methods[0];
409 if (methodDecl->isStatic())
410 return {};
411
412 if (macroNum == 1) {
413 // Save the number of parameters of the signal. The slot should not have more arguments.
414 senderMethod = methodDecl;
415 } else if (macroNum == 2) {
416 const unsigned int numReceiverParams = methodDecl->getNumParams();
417 if (numReceiverParams > senderMethod->getNumParams()) {
418 string msg = string("Receiver has more parameters (") + to_string(methodDecl->getNumParams()) + ") than signal (" + to_string(senderMethod->getNumParams()) + ')';
419 queueManualFixitWarning(s, msg);
420 return {};
421 }
422
423 for (unsigned int i = 0; i < numReceiverParams; ++i) {
424 ParmVarDecl *receiverParm = methodDecl->getParamDecl(i);
425 ParmVarDecl *senderParm = senderMethod->getParamDecl(i);
426 if (!clazy::isConvertibleTo(senderParm->getType().getTypePtr(), receiverParm->getType().getTypePtrOrNull())) {
427 string msg = string("Sender's parameters are incompatible with the receiver's");
428 queueManualFixitWarning(s, msg);
429 return {};
430 }
431 }
432 }
433
434 if ((classification & ConnectFlag_QTimerSingleShot) && methodDecl->getNumParams() > 0) {
435 string msg = "(QTimer) Fixit not implemented for slot with arguments, use a lambda";
436 queueManualFixitWarning(s, msg);
437 return {};
438 }
439
440 if ((classification & ConnectFlag_QMenuAddAction) && methodDecl->getNumParams() > 0) {
441 string msg = "(QMenu) Fixit not implemented for slot with arguments, use a lambda";
442 queueManualFixitWarning(s, msg);
443 return {};
444 }
445
446 DeclContext *context = m_context->lastDecl->getDeclContext();
447
448 bool isSpecialProtectedCase = false;
449 if (!clazy::canTakeAddressOf(methodDecl, context, /*by-ref*/ isSpecialProtectedCase)) {
450 string msg = "Can't fix " + clazy::accessString(methodDecl->getAccess()) + ' ' + macroName + ' ' + methodDecl->getQualifiedNameAsString();
451 queueManualFixitWarning(s, msg);
452 return {};
453 }
454
455 string qualifiedName;
456 auto contextRecord = clazy::firstContextOfType<CXXRecordDecl>(m_context->lastDecl->getDeclContext());
457 const bool isInInclude = sm().getMainFileID() != sm().getFileID(clazy::getLocStart(call));
458
459 if (isSpecialProtectedCase && contextRecord) {
460 // We're inside a derived class trying to take address of a protected base member, must use &Derived::method instead of &Base::method.
461 qualifiedName = contextRecord->getNameAsString() + "::" + methodDecl->getNameAsString();
462 } else {
463 qualifiedName = clazy::getMostNeededQualifiedName(sm(), methodDecl, context, clazy::getLocStart(call), !isInInclude); // (In includes ignore using directives)
464 }
465
466 CharSourceRange expansionRange = clazy::getImmediateExpansionRange(s, sm());
467 SourceRange range = SourceRange(expansionRange.getBegin(), expansionRange.getEnd());
468
469 const string functionPointer = '&' + qualifiedName;
470 string replacement = functionPointer;
471
472 if ((classification & ConnectFlag_4ArgsConnect) && macroNum == 2)
473 replacement = implicitCallee + ", " + replacement;
474
475 fixits.push_back(FixItHint::CreateReplacement(range, replacement));
476 lastRecordDecl = nullptr;
477 } else {
478 Expr *expr = arg;
479 const auto record = clazy::getBestDynamicClassType(expr);
480 if (record) {
481 lastRecordDecl = record;
482 if (isQPointer(expr)) {
483 auto endLoc = clazy::locForNextToken(&m_astContext, clazy::getLocStart(arg), tok::comma);
484 if (endLoc.isValid()) {
485 fixits.push_back(FixItHint::CreateInsertion(endLoc, ".data()"));
486 } else {
487 queueManualFixitWarning(s, "Can't fix this QPointer case");
488 return {};
489 }
490 }
491 }
492 }
493 }
494
495 return fixits;
496 }
497