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/Basic/SourceManager.h>
30#include <clang/Basic/Version.h>
31#include <clang/Lex/Lexer.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>
39lookUpCandidates(const clang::CXXRecordDecl *objClass, 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(Elt: *mi);
53 if (!d_func && (*mi)->getName() == "d_func" && !getResultType(decl: *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
59 ? nullptr
60 : classIt->bases_begin()->getType()->getAsCXXRecordDecl();
61
62 if (d_func && !classIt && candidates.empty()) {
63 classIt = getResultType(decl: d_func)->getPointeeCXXRecordDecl();
64 d_func = nullptr;
65 }
66 }
67 return candidates;
68}
69
70/**
71 * \a obj is an expression to a type of an QObject (or pointer to) that is the sender or the
72 * receiver \a method is an expression like SIGNAL(....) or SLOT(....)
73 *
74 * This function try to find the matching signal or slot declaration, and register its use.
75 */
76void QtSupport::handleSignalOrSlot(clang::Expr *obj, clang::Expr *method)
77{
78 if (!obj || !method)
79 return;
80 obj = obj->IgnoreImpCasts();
81 method = method->IgnoreImpCasts();
82 auto objType = obj->getType().getTypePtrOrNull();
83 if (!objType)
84 return;
85
86 const clang::CXXRecordDecl *objClass = objType->getPointeeCXXRecordDecl();
87 if (!objClass) {
88 // It can be a non-pointer if called like: foo.connect(....);
89 objClass = objType->getAsCXXRecordDecl();
90 if (!objClass)
91 return;
92 }
93
94 const clang::StringLiteral *methodLiteral = clang::dyn_cast<clang::StringLiteral>(Val: method);
95 if (!methodLiteral) {
96 // try qFlagLocation
97 clang::CallExpr *flagLoc = clang::dyn_cast<clang::CallExpr>(Val: method);
98
99 if (!flagLoc || flagLoc->getNumArgs() != 1 || !flagLoc->getDirectCallee()
100 || flagLoc->getDirectCallee()->getName() != "qFlagLocation")
101 return;
102
103
104 methodLiteral = clang::dyn_cast<clang::StringLiteral>(Val: flagLoc->getArg(Arg: 0)->IgnoreImpCasts());
105 if (!methodLiteral)
106 return;
107 }
108 if (methodLiteral->getCharByteWidth() != 1)
109 return;
110
111
112 auto signature = methodLiteral->getString().trim();
113 if (signature.size() < 4)
114 return;
115
116 if (signature.find(C: '\0') != signature.npos) {
117 signature = signature.substr(Start: 0, N: signature.find(C: '\0')).trim();
118 }
119
120 auto lParenPos = signature.find(C: '(');
121 auto rParenPos = signature.find(C: ')');
122 if (rParenPos == std::string::npos || rParenPos < lParenPos || lParenPos < 2)
123 return;
124
125 llvm::StringRef methodName = signature.slice(Start: 1, End: lParenPos).trim();
126
127 // Try to find the method which match this name in the given class or bases.
128 auto candidates = lookUpCandidates(objClass, methodName);
129
130 clang::LangOptions lo;
131 lo.CPlusPlus = true;
132 lo.Bool = true;
133 clang::PrintingPolicy policy(lo);
134 policy.SuppressScope = true;
135
136 auto argPos = lParenPos + 1;
137 unsigned int arg = 0;
138 while (argPos < signature.size() && !candidates.empty()) {
139
140 // Find next comma to extract the next argument
141 auto searchPos = argPos;
142 while (searchPos < signature.size() && signature[searchPos] != ','
143 && signature[searchPos] != ')') {
144 if (signature[searchPos] == '<') {
145 int depth = 0;
146 int templDepth = 0;
147 searchPos++;
148 while (searchPos < signature.size() && depth >= 0 && templDepth >= 0) {
149 switch (signature[searchPos]) {
150 case '(':
151 case '[':
152 case '{':
153 depth++;
154 break;
155 case ')':
156 case ']':
157 case '}':
158 depth--;
159 break;
160 case '>':
161 if (depth == 0)
162 templDepth--;
163 break;
164 case '<':
165 if (depth == 0)
166 templDepth++;
167 break;
168 }
169 ++searchPos;
170 }
171 continue;
172 }
173 ++searchPos;
174 }
175
176 if (searchPos == signature.size())
177 return;
178
179 llvm::StringRef argument = signature.substr(Start: argPos, N: searchPos - argPos).trim();
180 // Skip the const at the beginning
181
182 if (argument.startswith(Prefix: "const ") && argument.endswith(Suffix: "&"))
183 argument = argument.slice(Start: 6, End: argument.size() - 1).trim();
184
185 argPos = searchPos + 1;
186
187 if (argument.empty() && signature[searchPos] == ')' && arg == 0)
188 break; // No arguments
189
190
191 // Now go over the candidates and prune the impossible ones.
192 auto it = candidates.begin();
193 while (it != candidates.end()) {
194 if ((*it)->getNumParams() < arg + 1) {
195 // Not enough argument
196 it = candidates.erase(CI: it);
197 continue;
198 }
199
200 auto type = (*it)->getParamDecl(i: arg)->getType();
201
202 // remove const or const &
203 if (type->isReferenceType() && type.getNonReferenceType().isConstQualified())
204 type = type.getNonReferenceType();
205 type.removeLocalConst();
206
207 auto typeString_ = type.getAsString(Policy: policy);
208
209 auto typeString = llvm::StringRef(typeString_).trim();
210
211 // Now compare the two string without mathcin spaces,
212 auto sigIt = argument.begin();
213 auto parIt = typeString.begin();
214 while (sigIt != argument.end() && parIt != typeString.end()) {
215 if (*sigIt == *parIt) {
216 ++sigIt;
217 ++parIt;
218 } else if (*sigIt == ' ') {
219 ++sigIt;
220 } else if (*parIt == ' ') {
221 ++parIt;
222 } else if (*sigIt == 'n' && llvm::StringRef(sigIt, 9).startswith(Prefix: "nsigned ")) {
223 // skip unsigned
224 sigIt += 8;
225 } else if (*parIt == 'n' && llvm::StringRef(parIt, 9).startswith(Prefix: "nsigned ")) {
226 // skip unsigned
227 parIt += 8;
228 } else {
229 break;
230 }
231 }
232
233 if (sigIt != argument.end() || parIt != typeString.end()) {
234 // Did not match.
235 it = candidates.erase(CI: it);
236 continue;
237 }
238
239 ++it;
240 }
241
242 arg++;
243 if (signature[searchPos] == ')')
244 break;
245 }
246
247 if (argPos != signature.size())
248 return;
249
250
251 // Remove candidates that needs more argument
252 candidates.erase(
253 CS: std::remove_if(first: candidates.begin(), last: candidates.end(),
254 pred: [=](clang::CXXMethodDecl *it) {
255 return it->getMinRequiredArguments() > arg
256 && !(it->getNumParams() == arg + 1
257 && it->getParamDecl(i: arg)->getType().getAsString(Policy: policy)
258 == "QPrivateSignal");
259 }),
260 CE: candidates.end());
261
262 if (candidates.empty())
263 return;
264
265 auto used = candidates.front();
266
267
268
269 clang::SourceRange range = methodLiteral->getSourceRange();
270 if (methodLiteral->getNumConcatenated() >= 2) {
271 auto &sm = annotator.getSourceMgr();
272 // Goes two level up in the macro expension: First level is the # expansion, Second level
273 // is SIGNAL macro
274 auto r = sm.getImmediateExpansionRange(Loc: methodLiteral->getStrTokenLoc(TokNum: 1));
275#if CLANG_VERSION_MAJOR < 7
276 range = { sm.getImmediateExpansionRange(r.first).first,
277 sm.getImmediateExpansionRange(r.second).second };
278#else
279 range = { sm.getImmediateExpansionRange(Loc: r.getBegin()).getBegin(),
280 sm.getImmediateExpansionRange(Loc: r.getEnd()).getEnd() };
281#endif
282
283 // now remove the SIGNAL or SLOT macro from the range.
284 auto skip = clang::Lexer::MeasureTokenLength(Loc: range.getBegin(), SM: sm, LangOpts: annotator.getLangOpts());
285 range.setBegin(range.getBegin().getLocWithOffset(Offset: skip + 1));
286 // remove the ')' while we are on it
287 range.setEnd(range.getEnd().getLocWithOffset(Offset: -1));
288 }
289
290 annotator.registerUse(decl: used, range, tt: Annotator::Call, currentContext, useType: Annotator::Use_Address);
291}
292
293/**
294 * Very similar to handleSignalOrSlot, but does not handle the fact that the string might be in a
295 * macro and does the string contains the method name and not the full signature \a obj is an
296 * expression to a type of an QObject (or pointer to) that is the sender or the receiver \a method
297 * is an expression of type char*
298 *
299 * TODO: handle overloads
300 */
301void QtSupport::handleInvokeMethod(clang::Expr *obj, clang::Expr *method)
302{
303 if (!obj || !method)
304 return;
305 obj = obj->IgnoreImpCasts();
306 method = method->IgnoreImpCasts();
307 auto objType = obj->getType().getTypePtrOrNull();
308 if (!objType)
309 return;
310 const clang::CXXRecordDecl *objClass = objType->getPointeeCXXRecordDecl();
311 if (!objClass)
312 return;
313
314 const clang::StringLiteral *methodLiteral = clang::dyn_cast<clang::StringLiteral>(Val: method);
315 if (!methodLiteral)
316 return;
317 if (methodLiteral->getCharByteWidth() != 1)
318 return;
319
320 auto methodName = methodLiteral->getString();
321 if (methodName.empty())
322 return;
323
324 // Try to find the method which match this name in the given class or bases.
325 auto candidates = lookUpCandidates(objClass, methodName);
326 if (candidates.size() != 1)
327 return;
328 // FIXME: overloads resolution using the Q_ARG
329
330 auto used = candidates.front();
331 clang::SourceRange range = methodLiteral->getSourceRange();
332 annotator.registerUse(decl: used, range, tt: Annotator::Call, currentContext, useType: Annotator::Use_Address);
333}
334
335void QtSupport::visitCallExpr(clang::CallExpr *e)
336{
337 clang::CXXMethodDecl *methodDecl =
338 clang::dyn_cast_or_null<clang::CXXMethodDecl>(Val: e->getCalleeDecl());
339 if (!methodDecl || !methodDecl->getDeclName().isIdentifier() || !methodDecl->getParent())
340 return;
341 if (!methodDecl->getParent()->getDeclName().isIdentifier())
342 return;
343
344 auto parentName = methodDecl->getParent()->getName();
345
346 if (!parentName.startswith(Prefix: "Q"))
347 return; // only Qt classes
348
349 if (parentName == "QObject"
350 && (methodDecl->getName() == "connect" || methodDecl->getName() == "disconnect")) {
351 // We have a call to QObject::connect or disconnect
352 if (methodDecl->isStatic()) {
353 if (e->getNumArgs() >= 4) {
354 handleSignalOrSlot(obj: e->getArg(Arg: 0), method: e->getArg(Arg: 1));
355 handleSignalOrSlot(obj: e->getArg(Arg: 2), method: e->getArg(Arg: 3));
356 }
357 } else if (clang::CXXMemberCallExpr *me = clang::dyn_cast<clang::CXXMemberCallExpr>(Val: e)) {
358 if (e->getNumArgs() >= 3) {
359 handleSignalOrSlot(obj: e->getArg(Arg: 0), method: e->getArg(Arg: 1));
360 handleSignalOrSlot(obj: me->getImplicitObjectArgument(), method: e->getArg(Arg: 2));
361 }
362 }
363 }
364 if (parentName == "QTimer" && methodDecl->getName() == "singleShot") {
365 if (e->getNumArgs() >= 3) {
366 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 2));
367 }
368 }
369 if (parentName == "QHostInfo" && methodDecl->getName() == "lookupHost") {
370 if (e->getNumArgs() >= 3) {
371 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 2));
372 }
373 }
374 if (parentName == "QNetworkAccessCache" && methodDecl->getName() == "requestEntry") {
375 if (e->getNumArgs() >= 3) {
376 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 2));
377 }
378 }
379 if (parentName == "QDBusAbstractInterface" && methodDecl->getName() == "callWithCallback") {
380 if (e->getNumArgs() == 4) {
381 handleSignalOrSlot(obj: e->getArg(Arg: 2), method: e->getArg(Arg: 3));
382 } else if (e->getNumArgs() == 5) {
383 handleSignalOrSlot(obj: e->getArg(Arg: 2), method: e->getArg(Arg: 3));
384 handleSignalOrSlot(obj: e->getArg(Arg: 2), method: e->getArg(Arg: 4));
385 }
386 }
387 if (methodDecl->getName() == "open"
388 && (parentName == "QFileDialog" || parentName == "QColorDialog"
389 || parentName == "QFontDialog" || parentName == "QMessageBox"
390 || parentName == "QInputDialog" || parentName == "QPrintDialog"
391 || parentName == "QPageSetupDialog" || parentName == "QPrintPreviewDialog"
392 || parentName == "QProgressDialog")) {
393 if (e->getNumArgs() == 2) {
394 handleSignalOrSlot(obj: e->getArg(Arg: 0), method: e->getArg(Arg: 1));
395 }
396 }
397 if (parentName == "QMenu" && methodDecl->getName() == "addAction") {
398 if (methodDecl->getNumParams() == 4 && e->getNumArgs() >= 3) {
399 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 2));
400 } else if (methodDecl->getNumParams() == 5 && e->getNumArgs() >= 4) {
401 handleSignalOrSlot(obj: e->getArg(Arg: 2), method: e->getArg(Arg: 3));
402 }
403 }
404 if (parentName == "QToolbar" && methodDecl->getName() == "addAction") {
405 if (e->getNumArgs() == 3) {
406 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 2));
407 } else if (e->getNumArgs() == 4) {
408 handleSignalOrSlot(obj: e->getArg(Arg: 2), method: e->getArg(Arg: 3));
409 }
410 }
411 if (parentName == "QState" && methodDecl->getName() == "addTransition") {
412 if (e->getNumArgs() >= 2) {
413 handleSignalOrSlot(obj: e->getArg(Arg: 0), method: e->getArg(Arg: 1));
414 }
415 }
416 if (parentName == "QMetaObject" && methodDecl->getName() == "invokeMethod") {
417 if (e->getNumArgs() >= 2) {
418 handleInvokeMethod(obj: e->getArg(Arg: 0), method: e->getArg(Arg: 1));
419 }
420 }
421}
422
423void QtSupport::visitCXXConstructExpr(clang::CXXConstructExpr *e)
424{
425 clang::CXXConstructorDecl *methodDecl = e->getConstructor();
426 if (!methodDecl || !methodDecl->getParent())
427 return;
428
429 auto parent = methodDecl->getParent();
430 if (!parent->getName().startswith(Prefix: "Q"))
431 return; // only Qt classes
432
433 if (parent->getName() == "QShortcut") {
434 if (e->getNumArgs() >= 3)
435 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 2));
436 if (e->getNumArgs() >= 4)
437 handleSignalOrSlot(obj: e->getArg(Arg: 1), method: e->getArg(Arg: 3));
438 }
439 if (parent->getName() == "QSignalSpy" || parent->getName() == "QSignalTransition") {
440 if (e->getNumArgs() >= 2) {
441 handleSignalOrSlot(obj: e->getArg(Arg: 0), method: e->getArg(Arg: 1));
442 }
443 }
444}
445

source code of codebrowser/generator/qtsupport.cpp