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