bump product version to 5.0.4.1
[LibreOffice.git] / compilerplugins / clang / vclwidgets.cxx
blobfd79241a7dcdfbc2a13926dbf2e5fc83b92db6ba
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <string>
11 #include <iostream>
13 #include "plugin.hxx"
14 #include "compat.hxx"
15 #include "clang/AST/CXXInheritance.h"
17 // Final goal: Checker for VCL widget references. Makes sure that VCL Window subclasses are properly referenced counted and dispose()'ed.
19 // But at the moment it just finds subclasses of Window which are not heap-allocated
21 // TODO do I need to check for local and static variables, too ?
22 // TODO when we have a dispose() method, verify that the dispose() methods releases all of the Window references
23 // TODO when we have a dispose() method, verify that it calls the super-class dispose() method at some point.
25 namespace {
27 class VCLWidgets:
28 public RecursiveASTVisitor<VCLWidgets>, public loplugin::Plugin
30 public:
31 explicit VCLWidgets(InstantiationData const & data): Plugin(data) {}
33 virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
35 bool VisitVarDecl(const VarDecl *);
37 bool VisitFieldDecl(const FieldDecl *);
39 bool VisitParmVarDecl(const ParmVarDecl *);
41 bool VisitFunctionDecl(const FunctionDecl *);
43 bool VisitCXXDestructorDecl(const CXXDestructorDecl *);
45 bool VisitCXXDeleteExpr(const CXXDeleteExpr *);
47 bool VisitCallExpr(const CallExpr *);
48 bool VisitDeclRefExpr(const DeclRefExpr* pDeclRefExpr);
49 private:
50 bool isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl);
51 bool mbCheckingMemcpy = false;
54 static bool startsWith(const std::string& s, const char* other)
56 return s.compare(0, strlen(other), other) == 0;
59 bool BaseCheckNotWindowSubclass(const CXXRecordDecl *BaseDefinition, void *) {
60 if (BaseDefinition && BaseDefinition->getQualifiedNameAsString() == "OutputDevice") {
61 return false;
63 return true;
66 bool isDerivedFromWindow(const CXXRecordDecl *decl) {
67 if (!decl)
68 return false;
69 if (decl->getQualifiedNameAsString() == "OutputDevice")
70 return true;
71 if (!decl->hasDefinition()) {
72 return false;
74 if (// not sure what hasAnyDependentBases() does,
75 // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1
76 !decl->hasAnyDependentBases() &&
77 !decl->forallBases(BaseCheckNotWindowSubclass, nullptr, true)) {
78 return true;
80 return false;
83 bool containsWindowSubclass(const Type* pType0);
85 bool containsWindowSubclass(const QualType& qType) {
86 auto t = qType->getAs<RecordType>();
87 if (t != nullptr) {
88 auto d = dyn_cast<ClassTemplateSpecializationDecl>(t->getDecl());
89 if (d != nullptr) {
90 std::string name(d->getQualifiedNameAsString());
91 if (name == "ScopedVclPtr" || name == "ScopedVclPtrInstance"
92 || name == "VclPtr" || name == "VclPtrInstance")
94 return false;
98 return containsWindowSubclass(qType.getTypePtr());
101 bool containsWindowSubclass(const Type* pType0) {
102 if (!pType0)
103 return false;
104 const Type* pType = pType0->getUnqualifiedDesugaredType();
105 if (!pType)
106 return false;
107 const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
108 if (pRecordDecl) {
109 const ClassTemplateSpecializationDecl* pTemplate = dyn_cast<ClassTemplateSpecializationDecl>(pRecordDecl);
110 if (pTemplate) {
111 bool link = pTemplate->getQualifiedNameAsString() == "Link";
112 for(unsigned i=0; i<pTemplate->getTemplateArgs().size(); ++i) {
113 const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i];
114 if (rArg.getKind() == TemplateArgument::ArgKind::Type &&
115 containsWindowSubclass(rArg.getAsType()))
117 // OK for first template argument of tools/link.hxx Link
118 // to be a Window-derived pointer:
119 if (!link || i != 0) {
120 return true;
126 if (pType->isPointerType()) {
127 QualType pointeeType = pType->getPointeeType();
128 return containsWindowSubclass(pointeeType);
129 } else if (pType->isArrayType()) {
130 const ArrayType* pArrayType = dyn_cast<ArrayType>(pType);
131 QualType elementType = pArrayType->getElementType();
132 return containsWindowSubclass(elementType);
133 } else {
134 return isDerivedFromWindow(pRecordDecl);
138 bool VCLWidgets::VisitCXXDestructorDecl(const CXXDestructorDecl* pCXXDestructorDecl)
140 if (ignoreLocation(pCXXDestructorDecl)) {
141 return true;
143 if (!pCXXDestructorDecl->isThisDeclarationADefinition()) {
144 return true;
146 const CXXRecordDecl * pRecordDecl = pCXXDestructorDecl->getParent();
147 // ignore OutputDevice class
148 if (pRecordDecl->getQualifiedNameAsString() == "OutputDevice") {
149 return true;
151 // check if this class is derived from Window
152 if (!isDerivedFromWindow(pRecordDecl)) {
153 return true;
155 bool foundVclPtrField = false;
156 for(auto fieldDecl = pRecordDecl->field_begin();
157 fieldDecl != pRecordDecl->field_end(); ++fieldDecl)
159 const RecordType *pFieldRecordType = fieldDecl->getType()->getAs<RecordType>();
160 if (pFieldRecordType) {
161 const CXXRecordDecl *pFieldRecordTypeDecl = dyn_cast<CXXRecordDecl>(pFieldRecordType->getDecl());
162 if (startsWith(pFieldRecordTypeDecl->getQualifiedNameAsString(), "VclPtr")) {
163 foundVclPtrField = true;
164 break;
168 bool foundDispose = false;
169 for(auto methodDecl = pRecordDecl->method_begin();
170 methodDecl != pRecordDecl->method_end(); ++methodDecl)
172 if (methodDecl->isInstance() && methodDecl->param_size()==0 && methodDecl->getNameAsString() == "dispose") {
173 foundDispose = true;
174 break;
177 const CompoundStmt *pCompoundStatement = dyn_cast<CompoundStmt>(pCXXDestructorDecl->getBody());
178 // having an empty body and no dispose() method is fine
179 if (!foundVclPtrField && !foundDispose && pCompoundStatement && pCompoundStatement->size() == 0) {
180 return true;
182 if (foundVclPtrField && pCompoundStatement && pCompoundStatement->size() == 0) {
183 report(
184 DiagnosticsEngine::Warning,
185 "OutputDevice subclass with VclPtr field must call dispose() from its destructor.",
186 pCXXDestructorDecl->getLocStart())
187 << pCXXDestructorDecl->getSourceRange();
188 return true;
190 // check that the destructor for a OutputDevice subclass does nothing except call into the disposeOnce() method
191 bool ok = false;
192 if (pCompoundStatement) {
193 bool bFoundDisposeOnce = false;
194 int nNumExtraStatements = 0;
195 for(auto const * x : pCompoundStatement->body())
197 const CXXMemberCallExpr *pCallExpr = dyn_cast<CXXMemberCallExpr>(x);
198 if (pCallExpr) {
199 if( const FunctionDecl* func = pCallExpr->getDirectCallee()) {
200 if( func->getNumParams() == 0 && func->getIdentifier() != NULL
201 && ( func->getName() == "disposeOnce" )) {
202 bFoundDisposeOnce = true;
206 // checking for ParenExpr is a hacky way to ignore assert statements in older versions of clang (i.e. <= 3.2)
207 if (!pCallExpr && !dyn_cast<ParenExpr>(x))
208 nNumExtraStatements++;
210 ok = bFoundDisposeOnce && nNumExtraStatements == 0;
212 if (!ok) {
213 SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
214 pCXXDestructorDecl->getLocStart());
215 StringRef filename = compiler.getSourceManager().getFilename(spellingLocation);
216 if ( !(filename.startswith(SRCDIR "/vcl/source/window/window.cxx"))
217 && !(filename.startswith(SRCDIR "/vcl/source/gdi/virdev.cxx")) )
219 report(
220 DiagnosticsEngine::Warning,
221 "OutputDevice subclass should have nothing in its destructor but a call to disposeOnce().",
222 pCXXDestructorDecl->getLocStart())
223 << pCXXDestructorDecl->getSourceRange();
226 return true;
230 bool VCLWidgets::VisitVarDecl(const VarDecl * pVarDecl) {
231 if (ignoreLocation(pVarDecl)) {
232 return true;
234 if ( isa<ParmVarDecl>(pVarDecl) || pVarDecl->isLocalVarDecl() ) {
235 return true;
238 if ( !startsWith(pVarDecl->getType().getAsString(), "std::vector<vcl::Window *>")
239 && !startsWith(pVarDecl->getType().getAsString(), "std::map<vcl::Window *, Size>")
240 && !startsWith(pVarDecl->getType().getAsString(), "std::map<vcl::Window *, class Size>")
241 && !startsWith(pVarDecl->getType().getAsString(), "::std::vector<class Button *>")
242 && !startsWith(pVarDecl->getType().getAsString(), "::std::vector<Button *>")
243 && !startsWith(pVarDecl->getType().getAsString(), "::std::mem_fun1_t<")
244 && !startsWith(pVarDecl->getType().getAsString(), "::comphelper::mem_fun1_t<")
245 && !startsWith(pVarDecl->getType().getAsString(), "::std::pair<formula::RefButton *, formula::RefEdit *>")
246 && !startsWith(pVarDecl->getType().getAsString(), "::std::pair<RefButton *, RefEdit *>")
247 && !startsWith(pVarDecl->getType().getAsString(), "std::list<SwSidebarWin *>")
248 && !startsWith(pVarDecl->getType().getAsString(), "::std::map<OTableWindow *, sal_Int32>")
249 && !startsWith(pVarDecl->getType().getAsString(), "::std::map<class OTableWindow *, sal_Int32>")
250 && !startsWith(pVarDecl->getType().getAsString(), "::std::multimap<sal_Int32, OTableWindow *>")
251 && !startsWith(pVarDecl->getType().getAsString(), "::std::multimap<sal_Int32, class OTableWindow *>")
252 && !startsWith(pVarDecl->getType().getAsString(), "::dbp::OMultiInstanceAutoRegistration< ::dbp::OUnoAutoPilot<")
253 && !startsWith(pVarDecl->getType().getAsString(), "SwSidebarWin_iterator")
254 && !startsWith(pVarDecl->getType().getAsString(), "functor_vector_type")
255 && !startsWith(pVarDecl->getType().getAsString(), "const functor_vector_type")
256 && containsWindowSubclass(pVarDecl->getType()))
258 report(
259 DiagnosticsEngine::Warning,
260 "OutputDevice subclass should be wrapped in VclPtr. " + pVarDecl->getType().getAsString(),
261 pVarDecl->getLocation())
262 << pVarDecl->getSourceRange();
263 return true;
266 const RecordType *recordType = pVarDecl->getType()->getAs<RecordType>();
267 if (recordType == nullptr) {
268 return true;
270 const CXXRecordDecl *recordDecl = dyn_cast<CXXRecordDecl>(recordType->getDecl());
271 if (recordDecl == nullptr) {
272 return true;
274 // check if this field is derived from Window
275 if (isDerivedFromWindow(recordDecl)) {
276 report(
277 DiagnosticsEngine::Warning,
278 "OutputDevice subclass allocated on stack, should be allocated via VclPtr or via *.",
279 pVarDecl->getLocation())
280 << pVarDecl->getSourceRange();
282 return true;
285 bool VCLWidgets::VisitFieldDecl(const FieldDecl * fieldDecl) {
286 if (ignoreLocation(fieldDecl)) {
287 return true;
289 if (fieldDecl->isBitField()) {
290 return true;
292 const CXXRecordDecl *pParentRecordDecl = isa<RecordDecl>(fieldDecl->getDeclContext()) ? dyn_cast<CXXRecordDecl>(fieldDecl->getParent()) : nullptr;
293 if (containsWindowSubclass(fieldDecl->getType())) {
294 // have to ignore this for now, nasty reverse dependency from tools->vcl
295 if (!(pParentRecordDecl != nullptr && pParentRecordDecl->getQualifiedNameAsString() == "ErrorContextImpl")) {
296 report(
297 DiagnosticsEngine::Warning,
298 "OutputDevice subclass declared as a pointer field, should be wrapped in VclPtr." + fieldDecl->getType().getAsString(),
299 fieldDecl->getLocation())
300 << fieldDecl->getSourceRange();
301 return true;
304 const RecordType *recordType = fieldDecl->getType()->getAs<RecordType>();
305 if (recordType == nullptr) {
306 return true;
308 const CXXRecordDecl *recordDecl = dyn_cast<CXXRecordDecl>(recordType->getDecl());
309 if (recordDecl == nullptr) {
310 return true;
313 // check if this field is derived from Window
314 if (isDerivedFromWindow(recordDecl)) {
315 report(
316 DiagnosticsEngine::Warning,
317 "OutputDevice subclass allocated as a class member, should be allocated via VclPtr.",
318 fieldDecl->getLocation())
319 << fieldDecl->getSourceRange();
322 // If this field is a VclPtr field, then the class MUST have a dispose method
323 if (pParentRecordDecl && isDerivedFromWindow(pParentRecordDecl)
324 && startsWith(recordDecl->getQualifiedNameAsString(), "VclPtr"))
326 bool foundDispose = false;
327 for(auto methodDecl = pParentRecordDecl->method_begin();
328 methodDecl != pParentRecordDecl->method_end(); ++methodDecl)
330 if (methodDecl->isInstance() && methodDecl->param_size()==0 && methodDecl->getNameAsString() == "dispose") {
331 foundDispose = true;
332 break;
335 if (!foundDispose) {
336 report(
337 DiagnosticsEngine::Warning,
338 "OutputDevice subclass with a VclPtr field MUST have a dispose() method.",
339 fieldDecl->getLocation())
340 << fieldDecl->getSourceRange();
342 if (!pParentRecordDecl->hasUserDeclaredDestructor()) {
343 report(
344 DiagnosticsEngine::Warning,
345 "OutputDevice subclass with a VclPtr field MUST have an explicit destructor.",
346 fieldDecl->getLocation())
347 << fieldDecl->getSourceRange();
351 return true;
354 bool VCLWidgets::VisitParmVarDecl(ParmVarDecl const * pvDecl)
356 if (ignoreLocation(pvDecl)) {
357 return true;
359 // ignore the stuff in the VclPtr template class
360 const CXXMethodDecl *pMethodDecl = dyn_cast<CXXMethodDecl>(pvDecl->getDeclContext());
361 if (pMethodDecl
362 && pMethodDecl->getParent()->getQualifiedNameAsString().find("VclPtr") != std::string::npos) {
363 return true;
365 // we exclude this method in VclBuilder because it's so useful to have it like this
366 if (pMethodDecl
367 && pMethodDecl->getNameAsString() == "get"
368 && (pMethodDecl->getParent()->getQualifiedNameAsString() == "VclBuilder"
369 || pMethodDecl->getParent()->getQualifiedNameAsString() == "VclBuilderContainer"))
371 return true;
373 return true;
376 bool VCLWidgets::VisitFunctionDecl( const FunctionDecl* functionDecl )
378 if (ignoreLocation(functionDecl)) {
379 return true;
381 // ignore the stuff in the VclPtr template class
382 const CXXMethodDecl *pMethodDecl = dyn_cast<CXXMethodDecl>(functionDecl);
383 if (pMethodDecl
384 && pMethodDecl->getParent()->getQualifiedNameAsString() == "VclPtr") {
385 return true;
387 // ignore the OutputDevice::dispose() method
388 if (pMethodDecl
389 && pMethodDecl->getParent()->getQualifiedNameAsString() == "OutputDevice") {
390 return true;
392 if (functionDecl->hasBody() && pMethodDecl && isDerivedFromWindow(pMethodDecl->getParent())) {
393 // check the last thing that the dispose() method does, is to call into the superclass dispose method
394 if (pMethodDecl->getNameAsString() == "dispose") {
395 if (!isDisposeCallingSuperclassDispose(pMethodDecl)) {
396 report(
397 DiagnosticsEngine::Warning,
398 "OutputDevice subclass dispose() method MUST call dispose() of its superclass as the last thing it does",
399 functionDecl->getLocStart())
400 << functionDecl->getSourceRange();
405 return true;
408 bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr *pCXXDeleteExpr)
410 if (ignoreLocation(pCXXDeleteExpr)) {
411 return true;
413 const CXXRecordDecl *pPointee = pCXXDeleteExpr->getArgument()->getType()->getPointeeCXXRecordDecl();
414 if (pPointee && isDerivedFromWindow(pPointee)) {
415 SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(
416 pCXXDeleteExpr->getLocStart());
417 StringRef filename = compiler.getSourceManager().getFilename(spellingLocation);
418 if ( !(filename.startswith(SRCDIR "/include/vcl/outdev.hxx")))
420 report(
421 DiagnosticsEngine::Warning,
422 "calling delete on instance of OutputDevice subclass, must rather call disposeAndClear()",
423 pCXXDeleteExpr->getLocStart())
424 << pCXXDeleteExpr->getSourceRange();
427 const ImplicitCastExpr* pImplicitCastExpr = dyn_cast<ImplicitCastExpr>(pCXXDeleteExpr->getArgument());
428 if (!pImplicitCastExpr) {
429 return true;
431 if (pImplicitCastExpr->getCastKind() != CK_UserDefinedConversion) {
432 return true;
434 report(
435 DiagnosticsEngine::Warning,
436 "calling delete on instance of VclPtr, must rather call disposeAndClear()",
437 pCXXDeleteExpr->getLocStart())
438 << pCXXDeleteExpr->getSourceRange();
439 return true;
444 The AST looks like:
445 `-CXXMemberCallExpr 0xb06d8b0 'void'
446 `-MemberExpr 0xb06d868 '<bound member function type>' ->dispose 0x9d34880
447 `-ImplicitCastExpr 0xb06d8d8 'class SfxTabPage *' <UncheckedDerivedToBase (SfxTabPage)>
448 `-CXXThisExpr 0xb06d850 'class SfxAcceleratorConfigPage *' this
451 bool VCLWidgets::isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl)
453 const CompoundStmt *pCompoundStatement = dyn_cast<CompoundStmt>(pMethodDecl->getBody());
454 if (!pCompoundStatement) return false;
455 if (pCompoundStatement->size() == 0) return false;
456 // find the last statement
457 const CXXMemberCallExpr *pCallExpr = dyn_cast<CXXMemberCallExpr>(*pCompoundStatement->body_rbegin());
458 if (!pCallExpr) return false;
459 const MemberExpr *pMemberExpr = dyn_cast<MemberExpr>(pCallExpr->getCallee());
460 if (!pMemberExpr) return false;
461 if (pMemberExpr->getMemberDecl()->getNameAsString() != "dispose") return false;
462 const CXXMethodDecl *pDirectCallee = dyn_cast<CXXMethodDecl>(pCallExpr->getDirectCallee());
463 if (!pDirectCallee) return false;
464 /* Not working yet. Partially because sometimes the superclass does not a dispose() method, so it gets passed up the chain.
465 Need complex checking for that case.
466 if (pDirectCallee->getParent()->getTypeForDecl() != (*pMethodDecl->getParent()->bases_begin()).getType().getTypePtr()) {
467 report(
468 DiagnosticsEngine::Warning,
469 "dispose() method calling wrong baseclass, calling " + pDirectCallee->getParent()->getQualifiedNameAsString() +
470 " should be calling " + (*pMethodDecl->getParent()->bases_begin()).getType().getAsString(),
471 pCallExpr->getLocStart())
472 << pCallExpr->getSourceRange();
473 return false;
475 return true;
478 bool containsVclPtr(const Type* pType0);
480 bool containsVclPtr(const QualType& qType) {
481 auto t = qType->getAs<RecordType>();
482 if (t != nullptr) {
483 auto d = dyn_cast<ClassTemplateSpecializationDecl>(t->getDecl());
484 if (d != nullptr) {
485 std::string name(d->getQualifiedNameAsString());
486 if (name == "ScopedVclPtr" || name == "ScopedVclPtrInstance"
487 || name == "VclPtr" || name == "VclPtrInstance")
489 return true;
493 return containsVclPtr(qType.getTypePtr());
496 bool containsVclPtr(const Type* pType0) {
497 if (!pType0)
498 return false;
499 const Type* pType = pType0->getUnqualifiedDesugaredType();
500 if (!pType)
501 return false;
502 if (pType->isPointerType()) {
503 return false;
504 } else if (pType->isArrayType()) {
505 const ArrayType* pArrayType = dyn_cast<ArrayType>(pType);
506 QualType elementType = pArrayType->getElementType();
507 return containsVclPtr(elementType);
508 } else {
509 const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl();
510 if (pRecordDecl)
512 std::string name(pRecordDecl->getQualifiedNameAsString());
513 if (name == "ScopedVclPtr" || name == "ScopedVclPtrInstance"
514 || name == "VclPtr" || name == "VclPtrInstance")
516 return true;
518 for(auto fieldDecl = pRecordDecl->field_begin();
519 fieldDecl != pRecordDecl->field_end(); ++fieldDecl)
521 const RecordType *pFieldRecordType = fieldDecl->getType()->getAs<RecordType>();
522 if (pFieldRecordType && containsVclPtr(pFieldRecordType)) {
523 return true;
526 for(auto baseSpecifier = pRecordDecl->bases_begin();
527 baseSpecifier != pRecordDecl->bases_end(); ++baseSpecifier)
529 const RecordType *pFieldRecordType = baseSpecifier->getType()->getAs<RecordType>();
530 if (pFieldRecordType && containsVclPtr(pFieldRecordType)) {
531 return true;
536 return false;
539 bool VCLWidgets::VisitCallExpr(const CallExpr* pCallExpr)
541 if (ignoreLocation(pCallExpr)) {
542 return true;
544 FunctionDecl const * fdecl = pCallExpr->getDirectCallee();
545 if (fdecl == nullptr) {
546 return true;
548 std::string qname { fdecl->getQualifiedNameAsString() };
549 if (qname.find("memcpy") == std::string::npos
550 && qname.find("bcopy") == std::string::npos
551 && qname.find("memmove") == std::string::npos
552 && qname.find("rtl_copy") == std::string::npos) {
553 return true;
555 mbCheckingMemcpy = true;
556 Stmt * pStmt = const_cast<Stmt*>(static_cast<const Stmt*>(pCallExpr->getArg(0)));
557 TraverseStmt(pStmt);
558 mbCheckingMemcpy = false;
559 return true;
562 bool VCLWidgets::VisitDeclRefExpr(const DeclRefExpr* pDeclRefExpr)
564 if (!mbCheckingMemcpy) {
565 return true;
567 if (ignoreLocation(pDeclRefExpr)) {
568 return true;
570 QualType pType = pDeclRefExpr->getDecl()->getType();
571 if (pType->isPointerType()) {
572 pType = pType->getPointeeType();
574 if (!containsVclPtr(pType)) {
575 return true;
577 report(
578 DiagnosticsEngine::Warning,
579 "Calling memcpy on a type which contains a VclPtr",
580 pDeclRefExpr->getExprLoc());
581 return true;
585 loplugin::Plugin::Registration< VCLWidgets > X("vclwidgets");
589 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */