1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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.
28 public RecursiveASTVisitor
<VCLWidgets
>, public loplugin::Plugin
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
);
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") {
66 bool isDerivedFromWindow(const CXXRecordDecl
*decl
) {
69 if (decl
->getQualifiedNameAsString() == "OutputDevice")
71 if (!decl
->hasDefinition()) {
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)) {
83 bool containsWindowSubclass(const Type
* pType0
);
85 bool containsWindowSubclass(const QualType
& qType
) {
86 auto t
= qType
->getAs
<RecordType
>();
88 auto d
= dyn_cast
<ClassTemplateSpecializationDecl
>(t
->getDecl());
90 std::string
name(d
->getQualifiedNameAsString());
91 if (name
== "ScopedVclPtr" || name
== "ScopedVclPtrInstance"
92 || name
== "VclPtr" || name
== "VclPtrInstance")
98 return containsWindowSubclass(qType
.getTypePtr());
101 bool containsWindowSubclass(const Type
* pType0
) {
104 const Type
* pType
= pType0
->getUnqualifiedDesugaredType();
107 const CXXRecordDecl
* pRecordDecl
= pType
->getAsCXXRecordDecl();
109 const ClassTemplateSpecializationDecl
* pTemplate
= dyn_cast
<ClassTemplateSpecializationDecl
>(pRecordDecl
);
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) {
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
);
134 return isDerivedFromWindow(pRecordDecl
);
138 bool VCLWidgets::VisitCXXDestructorDecl(const CXXDestructorDecl
* pCXXDestructorDecl
)
140 if (ignoreLocation(pCXXDestructorDecl
)) {
143 if (!pCXXDestructorDecl
->isThisDeclarationADefinition()) {
146 const CXXRecordDecl
* pRecordDecl
= pCXXDestructorDecl
->getParent();
147 // ignore OutputDevice class
148 if (pRecordDecl
->getQualifiedNameAsString() == "OutputDevice") {
151 // check if this class is derived from Window
152 if (!isDerivedFromWindow(pRecordDecl
)) {
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;
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") {
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) {
182 if (foundVclPtrField
&& pCompoundStatement
&& pCompoundStatement
->size() == 0) {
184 DiagnosticsEngine::Warning
,
185 "OutputDevice subclass with VclPtr field must call dispose() from its destructor.",
186 pCXXDestructorDecl
->getLocStart())
187 << pCXXDestructorDecl
->getSourceRange();
190 // check that the destructor for a OutputDevice subclass does nothing except call into the disposeOnce() method
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
);
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;
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")) )
220 DiagnosticsEngine::Warning
,
221 "OutputDevice subclass should have nothing in its destructor but a call to disposeOnce().",
222 pCXXDestructorDecl
->getLocStart())
223 << pCXXDestructorDecl
->getSourceRange();
230 bool VCLWidgets::VisitVarDecl(const VarDecl
* pVarDecl
) {
231 if (ignoreLocation(pVarDecl
)) {
234 if ( isa
<ParmVarDecl
>(pVarDecl
) || pVarDecl
->isLocalVarDecl() ) {
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()))
259 DiagnosticsEngine::Warning
,
260 "OutputDevice subclass should be wrapped in VclPtr. " + pVarDecl
->getType().getAsString(),
261 pVarDecl
->getLocation())
262 << pVarDecl
->getSourceRange();
266 const RecordType
*recordType
= pVarDecl
->getType()->getAs
<RecordType
>();
267 if (recordType
== nullptr) {
270 const CXXRecordDecl
*recordDecl
= dyn_cast
<CXXRecordDecl
>(recordType
->getDecl());
271 if (recordDecl
== nullptr) {
274 // check if this field is derived from Window
275 if (isDerivedFromWindow(recordDecl
)) {
277 DiagnosticsEngine::Warning
,
278 "OutputDevice subclass allocated on stack, should be allocated via VclPtr or via *.",
279 pVarDecl
->getLocation())
280 << pVarDecl
->getSourceRange();
285 bool VCLWidgets::VisitFieldDecl(const FieldDecl
* fieldDecl
) {
286 if (ignoreLocation(fieldDecl
)) {
289 if (fieldDecl
->isBitField()) {
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")) {
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();
304 const RecordType
*recordType
= fieldDecl
->getType()->getAs
<RecordType
>();
305 if (recordType
== nullptr) {
308 const CXXRecordDecl
*recordDecl
= dyn_cast
<CXXRecordDecl
>(recordType
->getDecl());
309 if (recordDecl
== nullptr) {
313 // check if this field is derived from Window
314 if (isDerivedFromWindow(recordDecl
)) {
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") {
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()) {
344 DiagnosticsEngine::Warning
,
345 "OutputDevice subclass with a VclPtr field MUST have an explicit destructor.",
346 fieldDecl
->getLocation())
347 << fieldDecl
->getSourceRange();
354 bool VCLWidgets::VisitParmVarDecl(ParmVarDecl
const * pvDecl
)
356 if (ignoreLocation(pvDecl
)) {
359 // ignore the stuff in the VclPtr template class
360 const CXXMethodDecl
*pMethodDecl
= dyn_cast
<CXXMethodDecl
>(pvDecl
->getDeclContext());
362 && pMethodDecl
->getParent()->getQualifiedNameAsString().find("VclPtr") != std::string::npos
) {
365 // we exclude this method in VclBuilder because it's so useful to have it like this
367 && pMethodDecl
->getNameAsString() == "get"
368 && (pMethodDecl
->getParent()->getQualifiedNameAsString() == "VclBuilder"
369 || pMethodDecl
->getParent()->getQualifiedNameAsString() == "VclBuilderContainer"))
376 bool VCLWidgets::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
378 if (ignoreLocation(functionDecl
)) {
381 // ignore the stuff in the VclPtr template class
382 const CXXMethodDecl
*pMethodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
384 && pMethodDecl
->getParent()->getQualifiedNameAsString() == "VclPtr") {
387 // ignore the OutputDevice::dispose() method
389 && pMethodDecl
->getParent()->getQualifiedNameAsString() == "OutputDevice") {
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
)) {
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();
408 bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr
*pCXXDeleteExpr
)
410 if (ignoreLocation(pCXXDeleteExpr
)) {
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")))
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
) {
431 if (pImplicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
435 DiagnosticsEngine::Warning
,
436 "calling delete on instance of VclPtr, must rather call disposeAndClear()",
437 pCXXDeleteExpr
->getLocStart())
438 << pCXXDeleteExpr
->getSourceRange();
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()) {
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();
478 bool containsVclPtr(const Type
* pType0
);
480 bool containsVclPtr(const QualType
& qType
) {
481 auto t
= qType
->getAs
<RecordType
>();
483 auto d
= dyn_cast
<ClassTemplateSpecializationDecl
>(t
->getDecl());
485 std::string
name(d
->getQualifiedNameAsString());
486 if (name
== "ScopedVclPtr" || name
== "ScopedVclPtrInstance"
487 || name
== "VclPtr" || name
== "VclPtrInstance")
493 return containsVclPtr(qType
.getTypePtr());
496 bool containsVclPtr(const Type
* pType0
) {
499 const Type
* pType
= pType0
->getUnqualifiedDesugaredType();
502 if (pType
->isPointerType()) {
504 } else if (pType
->isArrayType()) {
505 const ArrayType
* pArrayType
= dyn_cast
<ArrayType
>(pType
);
506 QualType elementType
= pArrayType
->getElementType();
507 return containsVclPtr(elementType
);
509 const CXXRecordDecl
* pRecordDecl
= pType
->getAsCXXRecordDecl();
512 std::string
name(pRecordDecl
->getQualifiedNameAsString());
513 if (name
== "ScopedVclPtr" || name
== "ScopedVclPtrInstance"
514 || name
== "VclPtr" || name
== "VclPtrInstance")
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
)) {
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
)) {
539 bool VCLWidgets::VisitCallExpr(const CallExpr
* pCallExpr
)
541 if (ignoreLocation(pCallExpr
)) {
544 FunctionDecl
const * fdecl
= pCallExpr
->getDirectCallee();
545 if (fdecl
== nullptr) {
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
) {
555 mbCheckingMemcpy
= true;
556 Stmt
* pStmt
= const_cast<Stmt
*>(static_cast<const Stmt
*>(pCallExpr
->getArg(0)));
558 mbCheckingMemcpy
= false;
562 bool VCLWidgets::VisitDeclRefExpr(const DeclRefExpr
* pDeclRefExpr
)
564 if (!mbCheckingMemcpy
) {
567 if (ignoreLocation(pDeclRefExpr
)) {
570 QualType pType
= pDeclRefExpr
->getDecl()->getType();
571 if (pType
->isPointerType()) {
572 pType
= pType
->getPointeeType();
574 if (!containsVclPtr(pType
)) {
578 DiagnosticsEngine::Warning
,
579 "Calling memcpy on a type which contains a VclPtr",
580 pDeclRefExpr
->getExprLoc());
585 loplugin::Plugin::Registration
< VCLWidgets
> X("vclwidgets");
589 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */