1/****************************************************************************
2 * Copyright (C) 2012-2016 Woboq GmbH
3 * Olivier Goffart <contact at woboq.com>
4 * https://woboq.com/codebrowser.html
5 *
6 * This file is part of the Woboq Code Browser.
7 *
8 * Commercial License Usage:
9 * Licensees holding valid commercial licenses provided by Woboq may use
10 * this file in accordance with the terms contained in a written agreement
11 * between the licensee and Woboq.
12 * For further information see https://woboq.com/codebrowser.html
13 *
14 * Alternatively, this work may be used under a Creative Commons
15 * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License.
16 * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US
17 * This license does not allow you to use the code browser to assist the
18 * development of your commercial software. If you intent to do so, consider
19 * purchasing a commercial licence.
20 ****************************************************************************/
21
22/* This file handle the support of the SIGNAL and SLOT macro in QObject::connect */
23
24#include "qtsupport.h"
25#include "annotator.h"
26#include <clang/AST/DeclCXX.h>
27#include <clang/AST/ExprCXX.h>
28#include <clang/AST/PrettyPrinter.h>
29#include <clang/Lex/Lexer.h>
30#include <clang/Basic/SourceManager.h>
31#include <clang/Basic/Version.h>
32#include <llvm/Support/MemoryBuffer.h>
33
34/**
35 * Lookup candidates function of name \a methodName within the QObject derivative \a objClass
36 * its bases, or its private implementation
37 */
38static llvm::SmallVector<clang::CXXMethodDecl *, 10> lookUpCandidates(const clang::CXXRecordDecl* objClass,
39 llvm::StringRef methodName)
40{
41 llvm::SmallVector<clang::CXXMethodDecl *, 10> candidates;
42 clang::CXXMethodDecl *d_func = nullptr;
43 auto classIt = objClass;
44 while (classIt) {
45 if (!classIt->getDefinition())
46 break;
47
48 for (auto mi = classIt->method_begin(); mi != classIt->method_end(); ++mi) {
49 if (!(*mi)->getIdentifier())
50 continue;
51 if ((*mi)->getName() == methodName)
52 candidates.push_back(*mi);
53 if (!d_func && (*mi)->getName() == "d_func" && !getResultType(*mi).isNull())
54 d_func = *mi;
55 }
56
57 // Look in the first base (because the QObject need to be the first base class)
58 classIt = classIt->getNumBases() == 0 ? nullptr :
59 classIt->bases_begin()->getType()->getAsCXXRecordDecl();
60
61 if (d_func && !classIt && candidates.empty()) {
62 classIt = getResultType(d_func)->getPointeeCXXRecordDecl();
63 d_func = nullptr;
64 }
65 }
66 return candidates;
67}
68
69/**
70 * \a obj is an expression to a type of an QObject (or pointer to) that is the sender or the receiver
71 * \a method is an expression like SIGNAL(....) or SLOT(....)
72 *
73 * This function try to find the matching signal or slot declaration, and register its use.
74 */
75void QtSupport::handleSignalOrSlot(clang::Expr* obj, clang::Expr* method)
76{
77 if (!obj || !method) return;
78 obj = obj->IgnoreImpCasts();
79 method = method->IgnoreImpCasts();
80 auto objType = obj->getType().getTypePtrOrNull();
81 if (!objType) return;
82
83 const clang::CXXRecordDecl* objClass = objType->getPointeeCXXRecordDecl();
84 if (!objClass) {
85 // It can be a non-pointer if called like: foo.connect(....);
86 objClass = objType->getAsCXXRecordDecl();
87 if (!objClass) return;
88 }
89
90 const clang::StringLiteral *methodLiteral = clang::dyn_cast<clang::StringLiteral>(method);
91 if (!methodLiteral) {
92 // try qFlagLocation
93 clang::CallExpr *flagLoc = clang::dyn_cast<clang::CallExpr>(method);
94
95 if (!flagLoc || flagLoc->getNumArgs() != 1 || !flagLoc->getDirectCallee()
96 || flagLoc->getDirectCallee()->getName() != "qFlagLocation") return;
97
98
99 methodLiteral = clang::dyn_cast<clang::StringLiteral>(flagLoc->getArg(0)->IgnoreImpCasts());
100 if (!methodLiteral) return;
101 }
102 if (methodLiteral->getCharByteWidth() != 1) return;
103
104
105 auto signature = methodLiteral->getString().trim();
106 if (signature.size() < 4)
107 return;
108
109 if (signature.find('\0') != signature.npos) {
110 signature = signature.substr(0, signature.find('\0')).trim();
111 }
112
113 auto lParenPos = signature.find('(');
114 auto rParenPos = signature.find(')');
115 if (rParenPos == std::string::npos || rParenPos < lParenPos || lParenPos < 2)
116 return;
117
118 llvm::StringRef methodName = signature.slice(1 , lParenPos).trim();
119
120 // Try to find the method which match this name in the given class or bases.
121 auto candidates = lookUpCandidates(objClass, methodName);
122
123 clang::LangOptions lo;
124 lo.CPlusPlus = true;
125 lo.Bool = true;
126 clang::PrintingPolicy policy(lo);
127 policy.SuppressScope = true;
128
129 auto argPos = lParenPos + 1;
130 unsigned int arg = 0;
131 while (argPos < signature.size() && !candidates.empty()) {
132
133 // Find next comma to extract the next argument
134 auto searchPos = argPos;
135 while (searchPos < signature.size() && signature[searchPos] != ','
136 && signature[searchPos] != ')') {
137 if (signature[searchPos] == '<') {
138 int depth = 0;
139 int templDepth = 0;
140 searchPos++;
141 while(searchPos < signature.size() && depth >= 0 && templDepth >= 0) {
142 switch (signature[searchPos]) {
143 case '(': case '[': case '{': depth++; break;
144 case ')': case ']': case '}': depth--; break;
145 case '>': if (depth == 0) templDepth--; break;
146 case '<': if (depth == 0) templDepth++; break;
147 }
148 ++searchPos;
149 }
150 continue;
151 }
152 ++searchPos;
153 }
154
155 if (searchPos == signature.size())
156 return;
157
158 llvm::StringRef argument = signature.substr(argPos, searchPos - argPos).trim();
159 // Skip the const at the beginning
160
161 if (argument.startswith("const ") && argument.endswith("&"))
162 argument = argument.slice(6, argument.size()-1).trim();
163
164 argPos = searchPos + 1;
165
166 if (argument.empty() && signature[searchPos] == ')' && arg == 0)
167 break; //No arguments
168
169
170 //Now go over the candidates and prune the impossible ones.
171 auto it = candidates.begin();
172 while (it != candidates.end()) {
173 if ((*it)->getNumParams() < arg + 1) {
174 // Not enough argument
175 it = candidates.erase(it);
176 continue;
177 }
178
179 auto type = (*it)->getParamDecl(arg)->getType();
180
181 // remove const or const &
182 if (type->isReferenceType() && type.getNonReferenceType().isConstQualified())
183 type = type.getNonReferenceType();
184 type.removeLocalConst();
185
186 auto typeString_ = type.getAsString(policy);
187
188 auto typeString = llvm::StringRef(typeString_).trim();
189
190 // Now compare the two string without mathcin spaces,
191 auto sigIt = argument.begin();
192 auto parIt = typeString.begin();
193 while (sigIt != argument.end() && parIt != typeString.end()) {
194 if (*sigIt == *parIt) {
195 ++sigIt;
196 ++parIt;
197 } else if (*sigIt == ' ') {
198 ++sigIt;
199 } else if (*parIt == ' ') {
200 ++parIt;
201 } else if (*sigIt == 'n' && llvm::StringRef(sigIt, 9).startswith("nsigned ")) {
202 // skip unsigned
203 sigIt += 8;
204 } else if (*parIt == 'n' && llvm::StringRef(parIt, 9).startswith("nsigned ")) {
205 // skip unsigned
206 parIt += 8;
207 } else {
208 break;
209 }
210 }
211
212 if (sigIt != argument.end() || parIt != typeString.end()) {
213 // Did not match.
214 it = candidates.erase(it);
215 continue;
216 }
217
218 ++it;
219 }
220
221 arg++;
222 if (signature[searchPos] == ')')
223 break;
224 }
225
226 if (argPos != signature.size())
227 return;
228
229
230 // Remove candidates that needs more argument
231 candidates.erase(std::remove_if(candidates.begin(), candidates.end(), [=](clang::CXXMethodDecl *it) {
232 return it->getMinRequiredArguments() > arg &&
233 !(it->getNumParams() == arg+1 && it->getParamDecl(arg)->getType().getAsString(policy) == "QPrivateSignal");
234 }), candidates.end());
235
236 if (candidates.empty())
237 return;
238
239 auto used = candidates.front();
240
241
242
243 clang::SourceRange range = methodLiteral->getSourceRange();
244 if (methodLiteral->getNumConcatenated() >= 2) {
245 auto &sm = annotator.getSourceMgr();
246 // Goes two level up in the macro expension: First level is the # expansion, Second level is SIGNAL macro
247 auto r = sm.getImmediateExpansionRange(methodLiteral->getStrTokenLoc(1));
248#if CLANG_VERSION_MAJOR < 7
249 range = { sm.getImmediateExpansionRange(r.first).first, sm.getImmediateExpansionRange(r.second).second };
250#else
251 range = { sm.getImmediateExpansionRange(r.getBegin()).getBegin(), sm.getImmediateExpansionRange(r.getEnd()).getEnd() };
252#endif
253
254 // now remove the SIGNAL or SLOT macro from the range.
255 auto skip = clang::Lexer::MeasureTokenLength(range.getBegin(), sm, annotator.getLangOpts());
256 range.setBegin(range.getBegin().getLocWithOffset(skip+1));
257 // remove the ')' while we are on it
258 range.setEnd(range.getEnd().getLocWithOffset(-1));
259
260 }
261
262 annotator.registerUse(used, range, Annotator::Call, currentContext, Annotator::Use_Address);
263}
264
265/**
266 * Very similar to handleSignalOrSlot, but does not handle the fact that the string might be in a macro
267 * and does the string contains the method name and not the full signature
268 * \a obj is an expression to a type of an QObject (or pointer to) that is the sender or the receiver
269 * \a method is an expression of type char*
270 *
271 * TODO: handle overloads
272 */
273void QtSupport::handleInvokeMethod(clang::Expr* obj, clang::Expr* method)
274{
275 if (!obj || !method) return;
276 obj = obj->IgnoreImpCasts();
277 method = method->IgnoreImpCasts();
278 auto objType = obj->getType().getTypePtrOrNull();
279 if (!objType) return;
280 const clang::CXXRecordDecl* objClass = objType->getPointeeCXXRecordDecl();
281 if (!objClass) return;
282
283 const clang::StringLiteral *methodLiteral = clang::dyn_cast<clang::StringLiteral>(method);
284 if (!methodLiteral) return;
285 if (methodLiteral->getCharByteWidth() != 1) return;
286
287 auto methodName = methodLiteral->getString();
288 if (methodName.empty()) return;
289
290 // Try to find the method which match this name in the given class or bases.
291 auto candidates = lookUpCandidates(objClass, methodName);
292 if (candidates.size() != 1)
293 return;
294 // FIXME: overloads resolution using the Q_ARG
295
296 auto used = candidates.front();
297 clang::SourceRange range = methodLiteral->getSourceRange();
298 annotator.registerUse(used, range, Annotator::Call, currentContext, Annotator::Use_Address);
299}
300
301void QtSupport::visitCallExpr(clang::CallExpr* e)
302{
303 clang::CXXMethodDecl *methodDecl = clang::dyn_cast_or_null<clang::CXXMethodDecl>(e->getCalleeDecl());
304 if (!methodDecl || !methodDecl->getDeclName().isIdentifier() || !methodDecl->getParent())
305 return;
306 if (!methodDecl->getParent()->getDeclName().isIdentifier())
307 return;
308
309 auto parentName = methodDecl->getParent()->getName();
310
311 if (!parentName.startswith("Q"))
312 return; // only Qt classes
313
314 if (parentName == "QObject"
315 && (methodDecl->getName() == "connect" || methodDecl->getName() == "disconnect")) {
316 // We have a call to QObject::connect or disconnect
317 if (methodDecl->isStatic()) {
318 if (e->getNumArgs() >= 4) {
319 handleSignalOrSlot(e->getArg(0), e->getArg(1));
320 handleSignalOrSlot(e->getArg(2), e->getArg(3));
321 }
322 } else if (clang::CXXMemberCallExpr *me = clang::dyn_cast<clang::CXXMemberCallExpr>(e)) {
323 if (e->getNumArgs() >= 3) {
324 handleSignalOrSlot(e->getArg(0), e->getArg(1));
325 handleSignalOrSlot(me->getImplicitObjectArgument(), e->getArg(2));
326 }
327 }
328 }
329 if (parentName == "QTimer" && methodDecl->getName() == "singleShot") {
330 if (e->getNumArgs() >= 3) {
331 handleSignalOrSlot(e->getArg(1), e->getArg(2));
332 }
333 }
334 if (parentName == "QHostInfo" && methodDecl->getName() == "lookupHost") {
335 if (e->getNumArgs() >= 3) {
336 handleSignalOrSlot(e->getArg(1), e->getArg(2));
337 }
338 }
339 if (parentName == "QNetworkAccessCache" && methodDecl->getName() == "requestEntry") {
340 if (e->getNumArgs() >= 3) {
341 handleSignalOrSlot(e->getArg(1), e->getArg(2));
342 }
343 }
344 if (parentName == "QDBusAbstractInterface" && methodDecl->getName() == "callWithCallback") {
345 if (e->getNumArgs() == 4) {
346 handleSignalOrSlot(e->getArg(2), e->getArg(3));
347 } else if (e->getNumArgs() == 5) {
348 handleSignalOrSlot(e->getArg(2), e->getArg(3));
349 handleSignalOrSlot(e->getArg(2), e->getArg(4));
350 }
351 }
352 if (methodDecl->getName() == "open" && (
353 parentName == "QFileDialog" ||
354 parentName == "QColorDialog" ||
355 parentName == "QFontDialog" ||
356 parentName == "QMessageBox" ||
357 parentName == "QInputDialog" ||
358 parentName == "QPrintDialog" ||
359 parentName == "QPageSetupDialog" ||
360 parentName == "QPrintPreviewDialog" ||
361 parentName == "QProgressDialog")) {
362 if (e->getNumArgs() == 2) {
363 handleSignalOrSlot(e->getArg(0), e->getArg(1));
364 }
365 }
366 if (parentName == "QMenu" && methodDecl->getName() == "addAction") {
367 if (methodDecl->getNumParams() == 4 && e->getNumArgs() >= 3) {
368 handleSignalOrSlot(e->getArg(1), e->getArg(2));
369 } else if (methodDecl->getNumParams() == 5 && e->getNumArgs() >= 4) {
370 handleSignalOrSlot(e->getArg(2), e->getArg(3));
371 }
372 }
373 if (parentName == "QToolbar" && methodDecl->getName() == "addAction") {
374 if (e->getNumArgs() == 3) {
375 handleSignalOrSlot(e->getArg(1), e->getArg(2));
376 } else if (e->getNumArgs() == 4) {
377 handleSignalOrSlot(e->getArg(2), e->getArg(3));
378 }
379 }
380 if (parentName == "QState" && methodDecl->getName() == "addTransition") {
381 if (e->getNumArgs() >= 2) {
382 handleSignalOrSlot(e->getArg(0), e->getArg(1));
383 }
384 }
385 if (parentName == "QMetaObject" && methodDecl->getName() == "invokeMethod") {
386 if (e->getNumArgs() >= 2) {
387 handleInvokeMethod(e->getArg(0), e->getArg(1));
388 }
389 }
390}
391
392void QtSupport::visitCXXConstructExpr(clang::CXXConstructExpr* e)
393{
394 clang::CXXConstructorDecl *methodDecl = e->getConstructor();
395 if (!methodDecl || !methodDecl->getParent())
396 return;
397
398 auto parent = methodDecl->getParent();
399 if (!parent->getName().startswith("Q"))
400 return; // only Qt classes
401
402 if (parent->getName() == "QShortcut") {
403 if (e->getNumArgs() >= 3)
404 handleSignalOrSlot(e->getArg(1), e->getArg(2));
405 if (e->getNumArgs() >= 4)
406 handleSignalOrSlot(e->getArg(1), e->getArg(3));
407 }
408 if (parent->getName() == "QSignalSpy" || parent->getName() == "QSignalTransition") {
409 if (e->getNumArgs() >= 2) {
410 handleSignalOrSlot(e->getArg(0), e->getArg(1));
411 }
412 }
413}
414
415