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/.
16 #include "config_clang.h"
17 #include "clang/AST/CXXInheritance.h"
19 // Final goal: Checker for VCL widget references. Makes sure that VCL Window subclasses are properly referenced counted and dispose()'ed.
21 // But at the moment it just finds subclasses of Window which are not heap-allocated
23 // TODO do I need to check for local and static variables, too ?
24 // TODO when we have a dispose() method, verify that the dispose() methods releases all of the Window references
25 // TODO when we have a dispose() method, verify that it calls the super-class dispose() method at some point.
30 public loplugin::FilteringPlugin
<VCLWidgets
>
33 explicit VCLWidgets(loplugin::InstantiationData
const & data
): FilteringPlugin(data
)
36 virtual void run() override
{ TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()); }
38 bool shouldVisitTemplateInstantiations () const { return true; }
40 bool VisitVarDecl(const VarDecl
*);
41 bool VisitFieldDecl(const FieldDecl
*);
42 bool VisitParmVarDecl(const ParmVarDecl
*);
43 bool VisitFunctionDecl(const FunctionDecl
*);
44 bool VisitCXXDestructorDecl(const CXXDestructorDecl
*);
45 bool VisitCXXDeleteExpr(const CXXDeleteExpr
*);
46 bool VisitCallExpr(const CallExpr
*);
47 bool VisitDeclRefExpr(const DeclRefExpr
*);
48 bool VisitCXXConstructExpr(const CXXConstructExpr
*);
49 bool VisitBinaryOperator(const BinaryOperator
*);
51 void checkAssignmentForVclPtrToRawConversion(const SourceLocation
& sourceLoc
, const clang::Type
* lhsType
, const Expr
* rhs
);
52 bool isDisposeCallingSuperclassDispose(const CXXMethodDecl
* pMethodDecl
);
53 bool mbCheckingMemcpy
= false;
56 #define BASE_REF_COUNTED_CLASS "VclReferenceBase"
58 bool BaseCheckNotWindowSubclass(const CXXRecordDecl
*BaseDefinition
) {
59 return !loplugin::DeclCheck(BaseDefinition
).Class(BASE_REF_COUNTED_CLASS
)
63 bool isDerivedFromVclReferenceBase(const CXXRecordDecl
*decl
) {
66 if (loplugin::DeclCheck(decl
).Class(BASE_REF_COUNTED_CLASS
)
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
)) {
83 bool containsVclReferenceBaseSubclass(const clang::Type
* pType0
);
85 bool containsVclReferenceBaseSubclass(const QualType
& qType
) {
86 auto check
= loplugin::TypeCheck(qType
);
87 if (check
.Class("ScopedVclPtr").GlobalNamespace()
88 || check
.Class("ScopedVclPtrInstance").GlobalNamespace()
89 || check
.Class("VclPtr").GlobalNamespace()
90 || check
.Class("VclPtrInstance").GlobalNamespace())
94 return containsVclReferenceBaseSubclass(qType
.getTypePtr());
97 bool containsVclReferenceBaseSubclass(const clang::Type
* pType0
) {
100 const clang::Type
* pType
= pType0
->getUnqualifiedDesugaredType();
103 const CXXRecordDecl
* pRecordDecl
= pType
->getAsCXXRecordDecl();
105 const ClassTemplateSpecializationDecl
* pTemplate
= dyn_cast
<ClassTemplateSpecializationDecl
>(pRecordDecl
);
107 auto check
= loplugin::DeclCheck(pTemplate
);
108 if (check
.Class("VclStatusListener").GlobalNamespace()) {
111 bool link
= bool(check
.Class("Link").GlobalNamespace());
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 containsVclReferenceBaseSubclass(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 containsVclReferenceBaseSubclass(pointeeType
);
129 } else if (pType
->isArrayType()) {
130 const clang::ArrayType
* pArrayType
= dyn_cast
<clang::ArrayType
>(pType
);
131 QualType elementType
= pArrayType
->getElementType();
132 return containsVclReferenceBaseSubclass(elementType
);
134 return isDerivedFromVclReferenceBase(pRecordDecl
);
138 bool VCLWidgets::VisitCXXDestructorDecl(const CXXDestructorDecl
* pCXXDestructorDecl
)
140 if (ignoreLocation(pCXXDestructorDecl
)) {
143 if (!pCXXDestructorDecl
->isThisDeclarationADefinition()) {
146 const CXXRecordDecl
* pRecordDecl
= pCXXDestructorDecl
->getParent();
148 if (loplugin::DeclCheck(pRecordDecl
).Class(BASE_REF_COUNTED_CLASS
)
153 // check if this class is derived from VclReferenceBase
154 if (!isDerivedFromVclReferenceBase(pRecordDecl
)) {
157 // check if we have any VclPtr<> fields
158 bool bFoundVclPtrField
= false;
159 for(auto fieldDecl
= pRecordDecl
->field_begin();
160 fieldDecl
!= pRecordDecl
->field_end(); ++fieldDecl
)
162 const RecordType
*pFieldRecordType
= fieldDecl
->getType()->getAs
<RecordType
>();
163 if (pFieldRecordType
) {
164 if (loplugin::DeclCheck(pFieldRecordType
->getDecl())
165 .Class("VclPtr").GlobalNamespace())
167 bFoundVclPtrField
= true;
172 // check if there is a dispose() method
173 bool bFoundDispose
= false;
174 for(auto methodDecl
= pRecordDecl
->method_begin();
175 methodDecl
!= pRecordDecl
->method_end(); ++methodDecl
)
177 if (methodDecl
->isInstance() && methodDecl
->param_size()==0
178 && loplugin::DeclCheck(*methodDecl
).Function("dispose"))
180 bFoundDispose
= true;
184 const CompoundStmt
*pCompoundStatement
= dyn_cast_or_null
<CompoundStmt
>(pCXXDestructorDecl
->getBody());
185 // having an empty body and no dispose() method is fine
186 if (!bFoundVclPtrField
&& !bFoundDispose
&& (!pCompoundStatement
|| pCompoundStatement
->size() == 0)) {
189 if (bFoundVclPtrField
&& (!pCompoundStatement
|| pCompoundStatement
->size() == 0)) {
191 DiagnosticsEngine::Warning
,
192 BASE_REF_COUNTED_CLASS
" subclass with VclPtr field must call disposeOnce() from its destructor",
193 pCXXDestructorDecl
->getBeginLoc())
194 << pCXXDestructorDecl
->getSourceRange();
197 // Check that the destructor for a BASE_REF_COUNTED_CLASS subclass either
198 // only calls disposeOnce() or, if !bFoundVclPtrField, does nothing at all:
200 if (pCompoundStatement
) {
201 bool bFoundDisposeOnce
= false;
202 int nNumExtraStatements
= 0;
203 for (auto i
= pCompoundStatement
->body_begin();
204 i
!= pCompoundStatement
->body_end(); ++i
)
206 //TODO: The below erroneously also skips past entire statements like
208 // assert(true), ...;
211 for (auto loc
= (*i
)->getBeginLoc();
212 compiler
.getSourceManager().isMacroBodyExpansion(loc
);
213 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(
216 auto const name
= Lexer::getImmediateMacroName(
217 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
218 if (name
== "SAL_DEBUG" || name
== "assert") {
226 if (auto const pCallExpr
= dyn_cast
<CXXMemberCallExpr
>(*i
)) {
227 if( const FunctionDecl
* func
= pCallExpr
->getDirectCallee()) {
228 if( func
->getNumParams() == 0 && func
->getIdentifier() != NULL
229 && ( func
->getName() == "disposeOnce" )) {
230 bFoundDisposeOnce
= true;
235 nNumExtraStatements
++;
237 bOk
= (bFoundDisposeOnce
|| !bFoundVclPtrField
)
238 && nNumExtraStatements
== 0;
241 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
242 pCXXDestructorDecl
->getBeginLoc());
243 StringRef filename
= getFilenameOfLocation(spellingLocation
);
244 if ( !(loplugin::isSamePathname(filename
, SRCDIR
"/vcl/source/window/window.cxx"))
245 && !(loplugin::isSamePathname(filename
, SRCDIR
"/vcl/source/gdi/virdev.cxx"))
246 && !(loplugin::isSamePathname(filename
, SRCDIR
"/vcl/qa/cppunit/lifecycle.cxx"))
247 && !(loplugin::isSamePathname(filename
, SRCDIR
"/sfx2/source/dialog/tabdlg.cxx")) )
250 DiagnosticsEngine::Warning
,
251 BASE_REF_COUNTED_CLASS
" subclass should have nothing in its destructor but a call to disposeOnce()",
252 pCXXDestructorDecl
->getBeginLoc())
253 << pCXXDestructorDecl
->getSourceRange();
259 bool VCLWidgets::VisitBinaryOperator(const BinaryOperator
* binaryOperator
)
261 if (ignoreLocation(binaryOperator
)) {
264 if ( !binaryOperator
->isAssignmentOp() ) {
267 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
268 binaryOperator
->getBeginLoc());
269 checkAssignmentForVclPtrToRawConversion(spellingLocation
, binaryOperator
->getLHS()->getType().getTypePtr(), binaryOperator
->getRHS());
273 // Look for places where we are accidentally assigning a returned-by-value VclPtr<T> to a T*, which generally
274 // ends up in a use-after-free.
275 void VCLWidgets::checkAssignmentForVclPtrToRawConversion(const SourceLocation
& spellingLocation
, const clang::Type
* lhsType
, const Expr
* rhs
)
277 if (!lhsType
|| !isa
<clang::PointerType
>(lhsType
)) {
283 StringRef filename
= getFilenameOfLocation(spellingLocation
);
284 if (loplugin::isSamePathname(filename
, SRCDIR
"/include/rtl/ref.hxx")) {
287 const CXXRecordDecl
* pointeeClass
= lhsType
->getPointeeType()->getAsCXXRecordDecl();
288 if (!isDerivedFromVclReferenceBase(pointeeClass
)) {
292 // if we have T* on the LHS and VclPtr<T> on the RHS, we expect to see either
293 // an ImplicitCastExpr
294 // or an ExprWithCleanups and then an ImplicitCastExpr
295 if (auto implicitCastExpr
= dyn_cast
<ImplicitCastExpr
>(rhs
)) {
296 if (implicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
299 rhs
= rhs
->IgnoreCasts();
300 } else if (auto exprWithCleanups
= dyn_cast
<ExprWithCleanups
>(rhs
)) {
301 if (auto implicitCastExpr
= dyn_cast
<ImplicitCastExpr
>(exprWithCleanups
->getSubExpr())) {
302 if (implicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
305 rhs
= exprWithCleanups
->IgnoreCasts();
312 if (isa
<CXXNullPtrLiteralExpr
>(rhs
)) {
315 if (isa
<CXXThisExpr
>(rhs
)) {
319 // ignore assignments from a member field to a local variable, to avoid unnecessary refcounting traffic
320 if (auto callExpr
= dyn_cast
<CXXMemberCallExpr
>(rhs
)) {
321 if (auto calleeMemberExpr
= dyn_cast
<MemberExpr
>(callExpr
->getCallee())) {
322 if ((calleeMemberExpr
= dyn_cast
<MemberExpr
>(calleeMemberExpr
->getBase()->IgnoreImpCasts()))) {
323 if (isa
<FieldDecl
>(calleeMemberExpr
->getMemberDecl())) {
330 // ignore assignments from a local variable to a local variable, to avoid unnecessary refcounting traffic
331 if (auto callExpr
= dyn_cast
<CXXMemberCallExpr
>(rhs
)) {
332 if (auto calleeMemberExpr
= dyn_cast
<MemberExpr
>(callExpr
->getCallee())) {
333 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(calleeMemberExpr
->getBase()->IgnoreImpCasts())) {
334 if (isa
<VarDecl
>(declRefExpr
->getDecl())) {
340 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(rhs
->IgnoreImpCasts())) {
341 if (isa
<VarDecl
>(declRefExpr
->getDecl())) {
347 DiagnosticsEngine::Warning
,
348 "assigning a returned-by-value VclPtr<T> to a T* variable is dodgy, should be assigned to a VclPtr. If you know that the RHS does not return a newly created T, then add a '.get()' to the RHS",
349 rhs
->getSourceRange().getBegin())
350 << rhs
->getSourceRange();
353 bool VCLWidgets::VisitVarDecl(const VarDecl
* pVarDecl
) {
354 if (ignoreLocation(pVarDecl
)) {
357 if (isa
<ParmVarDecl
>(pVarDecl
)) {
360 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
361 pVarDecl
->getBeginLoc());
362 if (pVarDecl
->getInit()) {
363 checkAssignmentForVclPtrToRawConversion(spellingLocation
, pVarDecl
->getType().getTypePtr(), pVarDecl
->getInit());
365 StringRef aFileName
= getFilenameOfLocation(spellingLocation
);
366 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/vcl/vclptr.hxx"))
368 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/vcl/source/window/layout.cxx"))
370 // allowlist the valid things that can contain pointers.
371 // It is containing stuff like std::unique_ptr we get worried
372 if (pVarDecl
->getType()->isArrayType()) {
375 auto tc
= loplugin::TypeCheck(pVarDecl
->getType());
377 || tc
.Class("map").StdNamespace()
378 || tc
.Class("multimap").StdNamespace()
379 || tc
.Class("vector").StdNamespace()
380 || tc
.Class("list").StdNamespace()
381 || tc
.Class("mem_fun1_t").StdNamespace()
382 // registration template thing, doesn't actually allocate anything we need to care about
383 || tc
.Class("OMultiInstanceAutoRegistration").Namespace("compmodule").GlobalNamespace())
387 // Apparently I should be doing some kind of lookup for a partial specialisations of std::iterator_traits<T> to see if an
388 // object is an iterator, but that sounds like too much work
389 auto t
= pVarDecl
->getType().getDesugaredType(compiler
.getASTContext());
390 std::string s
= t
.getAsString();
391 if (s
.find("iterator") != std::string::npos
392 || loplugin::TypeCheck(t
).Class("__wrap_iter").StdNamespace())
396 // std::pair seems to show up in whacky ways in clang's AST. Sometimes it's a class, sometimes it's a typedef, and sometimes
397 // it's an ElaboratedType (whatever that is)
398 if (s
.find("pair") != std::string::npos
) {
402 if (containsVclReferenceBaseSubclass(pVarDecl
->getType())) {
404 DiagnosticsEngine::Warning
,
405 BASE_REF_COUNTED_CLASS
" subclass %0 should be wrapped in VclPtr",
406 pVarDecl
->getLocation())
407 << pVarDecl
->getType() << pVarDecl
->getSourceRange();
413 bool VCLWidgets::VisitFieldDecl(const FieldDecl
* fieldDecl
) {
414 if (ignoreLocation(fieldDecl
)) {
417 StringRef aFileName
= getFilenameOfLocation(
418 compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getBeginLoc()));
419 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/vcl/vclptr.hxx"))
421 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/rtl/ref.hxx"))
423 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/o3tl/enumarray.hxx"))
425 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/vcl/source/window/layout.cxx"))
427 if (fieldDecl
->isBitField()) {
430 const CXXRecordDecl
*pParentRecordDecl
= isa
<RecordDecl
>(fieldDecl
->getDeclContext()) ? dyn_cast
<CXXRecordDecl
>(fieldDecl
->getParent()) : nullptr;
431 if (loplugin::DeclCheck(pParentRecordDecl
).Class("VclPtr")
436 if (containsVclReferenceBaseSubclass(fieldDecl
->getType())) {
437 // have to ignore this for now, nasty reverse dependency from tools->vcl
438 auto check
= loplugin::DeclCheck(pParentRecordDecl
);
439 if (!(check
.Struct("ImplErrorContext").GlobalNamespace()
440 || check
.Class("ScHFEditPage").GlobalNamespace()))
443 DiagnosticsEngine::Warning
,
444 BASE_REF_COUNTED_CLASS
" subclass %0 declared as a pointer member, should be wrapped in VclPtr",
445 fieldDecl
->getLocation())
446 << fieldDecl
->getType() << fieldDecl
->getSourceRange();
447 if (auto parent
= dyn_cast
<ClassTemplateSpecializationDecl
>(fieldDecl
->getParent())) {
449 DiagnosticsEngine::Note
,
450 "template field here",
451 parent
->getPointOfInstantiation());
456 const RecordType
*recordType
= fieldDecl
->getType()->getAs
<RecordType
>();
457 if (recordType
== nullptr) {
460 const CXXRecordDecl
*recordDecl
= dyn_cast
<CXXRecordDecl
>(recordType
->getDecl());
461 if (recordDecl
== nullptr) {
465 // check if this field is derived fromVclReferenceBase
466 if (isDerivedFromVclReferenceBase(recordDecl
)) {
468 DiagnosticsEngine::Warning
,
469 BASE_REF_COUNTED_CLASS
" subclass allocated as a class member, should be allocated via VclPtr",
470 fieldDecl
->getLocation())
471 << fieldDecl
->getSourceRange();
474 // If this field is a VclPtr field, then the class MUST have a dispose method
475 if (pParentRecordDecl
&& isDerivedFromVclReferenceBase(pParentRecordDecl
)
476 && loplugin::DeclCheck(recordDecl
).Class("VclPtr").GlobalNamespace())
478 bool bFoundDispose
= false;
479 for(auto methodDecl
= pParentRecordDecl
->method_begin();
480 methodDecl
!= pParentRecordDecl
->method_end(); ++methodDecl
)
482 if (methodDecl
->isInstance() && methodDecl
->param_size()==0
483 && loplugin::DeclCheck(*methodDecl
).Function("dispose"))
485 bFoundDispose
= true;
489 if (!bFoundDispose
) {
491 DiagnosticsEngine::Warning
,
492 BASE_REF_COUNTED_CLASS
" subclass with a VclPtr field MUST override dispose() (and call its superclass dispose() as the last thing it does)",
493 fieldDecl
->getLocation())
494 << fieldDecl
->getSourceRange();
496 if (!pParentRecordDecl
->hasUserDeclaredDestructor()) {
498 DiagnosticsEngine::Warning
,
499 BASE_REF_COUNTED_CLASS
" subclass with a VclPtr field MUST have a user-provided destructor (that calls disposeOnce())",
500 fieldDecl
->getLocation())
501 << fieldDecl
->getSourceRange();
508 bool VCLWidgets::VisitParmVarDecl(ParmVarDecl
const * pvDecl
)
510 if (ignoreLocation(pvDecl
)) {
513 // ignore the stuff in the VclPtr template class
514 const CXXMethodDecl
*pMethodDecl
= dyn_cast
<CXXMethodDecl
>(pvDecl
->getDeclContext());
515 if (loplugin::DeclCheck(pMethodDecl
).MemberFunction().Class("VclPtr")
520 // we exclude this method in VclBuilder because it's so useful to have it like this
521 auto check
= loplugin::DeclCheck(pMethodDecl
).Function("get");
522 if (check
.Class("VclBuilder").GlobalNamespace()
523 || check
.Class("VclBuilderContainer").GlobalNamespace())
531 static void findDisposeAndClearStatements(std::set
<const FieldDecl
*>& aVclPtrFields
, const Stmt
*pStmt
)
535 if (isa
<CompoundStmt
>(pStmt
)) {
536 const CompoundStmt
*pCompoundStatement
= dyn_cast
<CompoundStmt
>(pStmt
);
537 for (auto i
= pCompoundStatement
->body_begin();
538 i
!= pCompoundStatement
->body_end(); ++i
)
540 findDisposeAndClearStatements(aVclPtrFields
, *i
);
544 if (isa
<ForStmt
>(pStmt
)) {
545 findDisposeAndClearStatements(aVclPtrFields
, dyn_cast
<ForStmt
>(pStmt
)->getBody());
548 if (isa
<IfStmt
>(pStmt
)) {
549 findDisposeAndClearStatements(aVclPtrFields
, dyn_cast
<IfStmt
>(pStmt
)->getThen());
550 findDisposeAndClearStatements(aVclPtrFields
, dyn_cast
<IfStmt
>(pStmt
)->getElse());
553 if (!isa
<CallExpr
>(pStmt
)) return;
554 const CallExpr
*pCallExpr
= dyn_cast
<CallExpr
>(pStmt
);
556 if (!pCallExpr
->getDirectCallee()) return;
557 if (!isa
<CXXMethodDecl
>(pCallExpr
->getDirectCallee())) return;
558 auto check
= loplugin::DeclCheck(
559 dyn_cast
<CXXMethodDecl
>(pCallExpr
->getDirectCallee()));
560 if (!(check
.Function("disposeAndClear") || check
.Function("clear")))
563 if (!pCallExpr
->getCallee()) return;
565 if (!isa
<MemberExpr
>(pCallExpr
->getCallee())) return;
566 const MemberExpr
*pCalleeMemberExpr
= dyn_cast
<MemberExpr
>(pCallExpr
->getCallee());
568 if (!pCalleeMemberExpr
->getBase()) return;
569 const MemberExpr
*pCalleeMemberExprBase
= dyn_cast
<MemberExpr
>(pCalleeMemberExpr
->getBase()->IgnoreImpCasts());
570 if (pCalleeMemberExprBase
== nullptr) return;
572 const FieldDecl
* xxx
= dyn_cast_or_null
<FieldDecl
>(pCalleeMemberExprBase
->getMemberDecl());
574 aVclPtrFields
.erase(xxx
);
578 bool VCLWidgets::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
580 if (ignoreLocation(functionDecl
)) {
583 // ignore the stuff in the VclPtr template class
584 if (loplugin::DeclCheck(functionDecl
).MemberFunction().Class("VclPtr")
589 // ignore the BASE_REF_COUNTED_CLASS::dispose() method
590 if (loplugin::DeclCheck(functionDecl
).Function("dispose")
591 .Class(BASE_REF_COUNTED_CLASS
).GlobalNamespace())
595 const CXXMethodDecl
*pMethodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
596 if (functionDecl
->hasBody() && pMethodDecl
&& isDerivedFromVclReferenceBase(pMethodDecl
->getParent())) {
597 // check the last thing that the dispose() method does, is to call into the superclass dispose method
598 if (loplugin::DeclCheck(functionDecl
).Function("dispose")) {
599 if (!isDisposeCallingSuperclassDispose(pMethodDecl
)) {
600 // We specifically have to clear a member variable AFTER calling super::dispose() here, unfortunately
601 if (!loplugin::DeclCheck(pMethodDecl
->getParent()).Class("WindowOutputDevice"))
603 DiagnosticsEngine::Warning
,
604 BASE_REF_COUNTED_CLASS
" subclass dispose() function MUST call dispose() of its superclass as the last thing it does",
605 functionDecl
->getBeginLoc())
606 << functionDecl
->getSourceRange();
611 // check dispose method to make sure we are actually disposing all of the VclPtr fields
612 // FIXME this is not exhaustive. We should enable shouldVisitTemplateInstantiations and look deeper inside type declarations
613 if (pMethodDecl
&& pMethodDecl
->isInstance() && pMethodDecl
->getBody()
614 && pMethodDecl
->param_size()==0
615 && loplugin::DeclCheck(functionDecl
).Function("dispose")
616 && isDerivedFromVclReferenceBase(pMethodDecl
->getParent()) )
618 auto check
= loplugin::DeclCheck(functionDecl
).MemberFunction();
619 if (check
.Class("VirtualDevice").GlobalNamespace()
620 || check
.Class("Breadcrumb").GlobalNamespace())
625 std::set
<const FieldDecl
*> aVclPtrFields
;
626 for (auto i
= pMethodDecl
->getParent()->field_begin();
627 i
!= pMethodDecl
->getParent()->field_end(); ++i
)
629 auto const type
= loplugin::TypeCheck((*i
)->getType());
630 if (type
.Class("VclPtr").GlobalNamespace()) {
631 aVclPtrFields
.insert(*i
);
632 } else if (type
.Class("vector").StdNamespace()
633 || type
.Class("map").StdNamespace()
634 || type
.Class("list").StdNamespace()
635 || type
.Class("set").StdNamespace())
637 const RecordType
* recordType
= dyn_cast_or_null
<RecordType
>((*i
)->getType()->getUnqualifiedDesugaredType());
639 auto d
= dyn_cast
<ClassTemplateSpecializationDecl
>(recordType
->getDecl());
640 if (d
&& d
->getTemplateArgs().size()>0) {
641 auto const type
= loplugin::TypeCheck(d
->getTemplateArgs()[0].getAsType());
642 if (type
.Class("VclPtr").GlobalNamespace()) {
643 aVclPtrFields
.insert(*i
);
649 if (!aVclPtrFields
.empty()) {
650 findDisposeAndClearStatements( aVclPtrFields
, pMethodDecl
->getBody() );
651 if (!aVclPtrFields
.empty()) {
652 //pMethodDecl->dump();
653 std::string aMessage
= BASE_REF_COUNTED_CLASS
" subclass dispose() method does not call disposeAndClear() or clear() on the following field(s): ";
654 for(auto s
: aVclPtrFields
)
655 aMessage
+= ", " + s
->getNameAsString();
657 DiagnosticsEngine::Warning
,
659 functionDecl
->getBeginLoc())
660 << functionDecl
->getSourceRange();
668 bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr
*pCXXDeleteExpr
)
670 if (ignoreLocation(pCXXDeleteExpr
)) {
673 const CXXRecordDecl
*pPointee
= pCXXDeleteExpr
->getArgument()->getType()->getPointeeCXXRecordDecl();
674 if (pPointee
&& isDerivedFromVclReferenceBase(pPointee
)) {
675 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
676 pCXXDeleteExpr
->getBeginLoc());
677 StringRef filename
= getFilenameOfLocation(spellingLocation
);
678 if ( !(loplugin::isSamePathname(filename
, SRCDIR
"/include/vcl/vclreferencebase.hxx")))
681 DiagnosticsEngine::Warning
,
682 "calling delete on instance of " BASE_REF_COUNTED_CLASS
" subclass, must rather call disposeAndClear()",
683 pCXXDeleteExpr
->getBeginLoc())
684 << pCXXDeleteExpr
->getSourceRange();
687 const ImplicitCastExpr
* pImplicitCastExpr
= dyn_cast
<ImplicitCastExpr
>(pCXXDeleteExpr
->getArgument());
688 if (!pImplicitCastExpr
) {
691 if (pImplicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
694 if (!loplugin::TypeCheck(pImplicitCastExpr
->getSubExprAsWritten()->getType()).Class("VclPtr")
700 DiagnosticsEngine::Warning
,
701 "calling delete on instance of VclPtr, must rather call disposeAndClear()",
702 pCXXDeleteExpr
->getBeginLoc())
703 << pCXXDeleteExpr
->getSourceRange();
710 `-CXXMemberCallExpr 0xb06d8b0 'void'
711 `-MemberExpr 0xb06d868 '<bound member function type>' ->dispose 0x9d34880
712 `-ImplicitCastExpr 0xb06d8d8 'class SfxTabPage *' <UncheckedDerivedToBase (SfxTabPage)>
713 `-CXXThisExpr 0xb06d850 'class SfxAcceleratorConfigPage *' this
716 bool VCLWidgets::isDisposeCallingSuperclassDispose(const CXXMethodDecl
* pMethodDecl
)
718 const CompoundStmt
*pCompoundStatement
= dyn_cast
<CompoundStmt
>(pMethodDecl
->getBody());
719 if (!pCompoundStatement
) return false;
720 if (pCompoundStatement
->size() == 0) return false;
721 // find the last statement
722 const CXXMemberCallExpr
*pCallExpr
= dyn_cast
<CXXMemberCallExpr
>(*pCompoundStatement
->body_rbegin());
723 if (!pCallExpr
) return false;
724 const MemberExpr
*pMemberExpr
= dyn_cast
<MemberExpr
>(pCallExpr
->getCallee());
725 if (!pMemberExpr
) return false;
726 if (!loplugin::DeclCheck(pMemberExpr
->getMemberDecl()).Function("dispose")) return false;
727 const CXXMethodDecl
*pDirectCallee
= dyn_cast
<CXXMethodDecl
>(pCallExpr
->getDirectCallee());
728 if (!pDirectCallee
) return false;
729 /* Not working yet. Partially because sometimes the superclass does not a dispose() method, so it gets passed up the chain.
730 Need complex checking for that case.
731 if (pDirectCallee->getParent()->getTypeForDecl() != (*pMethodDecl->getParent()->bases_begin()).getType().getTypePtr()) {
733 DiagnosticsEngine::Warning,
734 "dispose() method calling wrong baseclass, calling " + pDirectCallee->getParent()->getQualifiedNameAsString() +
735 " should be calling " + (*pMethodDecl->getParent()->bases_begin()).getType().getAsString(),
736 pCallExpr->getLocStart())
737 << pCallExpr->getSourceRange();
743 bool containsVclPtr(const clang::Type
* pType0
);
745 bool containsVclPtr(const QualType
& qType
) {
746 auto check
= loplugin::TypeCheck(qType
);
747 if (check
.Class("ScopedVclPtr").GlobalNamespace()
748 || check
.Class("ScopedVclPtrInstance").GlobalNamespace()
749 || check
.Class("VclPtr").GlobalNamespace()
750 || check
.Class("VclPtrInstance").GlobalNamespace())
754 return containsVclPtr(qType
.getTypePtr());
757 bool containsVclPtr(const clang::Type
* pType0
) {
760 const clang::Type
* pType
= pType0
->getUnqualifiedDesugaredType();
763 if (pType
->isPointerType()) {
765 } else if (pType
->isArrayType()) {
766 const clang::ArrayType
* pArrayType
= dyn_cast
<clang::ArrayType
>(pType
);
767 QualType elementType
= pArrayType
->getElementType();
768 return containsVclPtr(elementType
);
770 const CXXRecordDecl
* pRecordDecl
= pType
->getAsCXXRecordDecl();
773 auto check
= loplugin::DeclCheck(pRecordDecl
);
774 if (check
.Class("ScopedVclPtr").GlobalNamespace()
775 || check
.Class("ScopedVclPtrInstance").GlobalNamespace()
776 || check
.Class("VclPtr").GlobalNamespace()
777 || check
.Class("VclPtrInstance").GlobalNamespace())
781 for(auto fieldDecl
= pRecordDecl
->field_begin();
782 fieldDecl
!= pRecordDecl
->field_end(); ++fieldDecl
)
784 const RecordType
*pFieldRecordType
= fieldDecl
->getType()->getAs
<RecordType
>();
785 if (pFieldRecordType
&& containsVclPtr(pFieldRecordType
)) {
789 for(auto baseSpecifier
= pRecordDecl
->bases_begin();
790 baseSpecifier
!= pRecordDecl
->bases_end(); ++baseSpecifier
)
792 const RecordType
*pFieldRecordType
= baseSpecifier
->getType()->getAs
<RecordType
>();
793 if (pFieldRecordType
&& containsVclPtr(pFieldRecordType
)) {
802 bool VCLWidgets::VisitCallExpr(const CallExpr
* pCallExpr
)
804 if (ignoreLocation(pCallExpr
)) {
807 FunctionDecl
const * fdecl
= pCallExpr
->getDirectCallee();
808 if (fdecl
== nullptr) {
811 std::string qname
{ fdecl
->getQualifiedNameAsString() };
812 if (qname
.find("memcpy") == std::string::npos
813 && qname
.find("bcopy") == std::string::npos
814 && qname
.find("memmove") == std::string::npos
815 && qname
.find("rtl_copy") == std::string::npos
) {
818 mbCheckingMemcpy
= true;
819 Stmt
* pStmt
= const_cast<Stmt
*>(static_cast<const Stmt
*>(pCallExpr
->getArg(0)));
821 mbCheckingMemcpy
= false;
825 bool VCLWidgets::VisitDeclRefExpr(const DeclRefExpr
* pDeclRefExpr
)
827 if (!mbCheckingMemcpy
) {
830 if (ignoreLocation(pDeclRefExpr
)) {
833 QualType pType
= pDeclRefExpr
->getDecl()->getType();
834 if (pType
->isPointerType()) {
835 pType
= pType
->getPointeeType();
837 if (!containsVclPtr(pType
)) {
841 DiagnosticsEngine::Warning
,
842 "Calling memcpy on a type which contains a VclPtr",
843 pDeclRefExpr
->getExprLoc());
847 bool VCLWidgets::VisitCXXConstructExpr( const CXXConstructExpr
* constructExpr
)
849 if (ignoreLocation(constructExpr
)) {
852 if (constructExpr
->getConstructionKind() != CXXConstructExpr::CK_Complete
) {
855 const CXXConstructorDecl
* pConstructorDecl
= constructExpr
->getConstructor();
856 const CXXRecordDecl
* recordDecl
= pConstructorDecl
->getParent();
857 if (isDerivedFromVclReferenceBase(recordDecl
)) {
858 StringRef aFileName
= getFilenameOfLocation(
859 compiler
.getSourceManager().getSpellingLoc(constructExpr
->getBeginLoc()));
860 if (!loplugin::isSamePathname(aFileName
, SRCDIR
"/include/vcl/vclptr.hxx")) {
862 DiagnosticsEngine::Warning
,
863 "Calling constructor of a VclReferenceBase-derived type directly; all such creation should go via VclPtr<>::Create",
864 constructExpr
->getExprLoc());
870 loplugin::Plugin::Registration
< VCLWidgets
> vclwidgets("vclwidgets");
874 // Cannot be shared, uses TraverseStmt().
876 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */