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 | */ |
38 | static llvm::SmallVector<clang::CXXMethodDecl *, 10> |
39 | lookUpCandidates(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 | */ |
76 | void 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 | */ |
301 | void 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 | |
335 | void 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 | |
423 | void 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 | |