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 | */ |
38 | static 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 | */ |
75 | void 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 | */ |
273 | void 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 | |
301 | void 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 | |
392 | void 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 | |