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 "clang/AST/CXXInheritance.h"
18 // Final goal: Checker for VCL widget references. Makes sure that VCL Window subclasses are properly referenced counted and dispose()'ed.
20 // But at the moment it just finds subclasses of Window which are not heap-allocated
22 // TODO do I need to check for local and static variables, too ?
23 // TODO when we have a dispose() method, verify that the dispose() methods releases all of the Window references
24 // TODO when we have a dispose() method, verify that it calls the super-class dispose() method at some point.
29 public RecursiveASTVisitor
<VCLWidgets
>, public loplugin::Plugin
32 explicit VCLWidgets(InstantiationData
const & data
): Plugin(data
) {}
34 virtual void run() override
{ TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()); }
36 bool shouldVisitTemplateInstantiations () const { return true; }
38 bool VisitVarDecl(const VarDecl
*);
39 bool VisitFieldDecl(const FieldDecl
*);
40 bool VisitParmVarDecl(const ParmVarDecl
*);
41 bool VisitFunctionDecl(const FunctionDecl
*);
42 bool VisitCXXDestructorDecl(const CXXDestructorDecl
*);
43 bool VisitCXXDeleteExpr(const CXXDeleteExpr
*);
44 bool VisitCallExpr(const CallExpr
*);
45 bool VisitDeclRefExpr(const DeclRefExpr
*);
46 bool VisitCXXConstructExpr(const CXXConstructExpr
*);
47 bool VisitBinaryOperator(const BinaryOperator
*);
49 void checkAssignmentForVclPtrToRawConversion(const SourceLocation
& sourceLoc
, const Type
* lhsType
, const Expr
* rhs
);
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 #define BASE_REF_COUNTED_CLASS "VclReferenceBase"
61 bool BaseCheckNotWindowSubclass(
62 const CXXRecordDecl
*BaseDefinition
63 #if CLANG_VERSION < 30800
68 if (BaseDefinition
&& BaseDefinition
->getQualifiedNameAsString() == BASE_REF_COUNTED_CLASS
) {
74 bool isDerivedFromVclReferenceBase(const CXXRecordDecl
*decl
) {
77 if (decl
->getQualifiedNameAsString() == BASE_REF_COUNTED_CLASS
)
79 if (!decl
->hasDefinition()) {
82 if (// not sure what hasAnyDependentBases() does,
83 // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1
84 !decl
->hasAnyDependentBases() &&
85 !compat::forallBases(*decl
, BaseCheckNotWindowSubclass
, nullptr, true)) {
91 bool containsVclReferenceBaseSubclass(const Type
* pType0
);
93 bool containsVclReferenceBaseSubclass(const QualType
& qType
) {
94 auto t
= qType
->getAs
<RecordType
>();
96 auto d
= dyn_cast
<ClassTemplateSpecializationDecl
>(t
->getDecl());
98 std::string
name(d
->getQualifiedNameAsString());
99 if (name
== "ScopedVclPtr" || name
== "ScopedVclPtrInstance"
100 || name
== "VclPtr" || name
== "VclPtrInstance")
106 return containsVclReferenceBaseSubclass(qType
.getTypePtr());
109 bool containsVclReferenceBaseSubclass(const Type
* pType0
) {
112 const Type
* pType
= pType0
->getUnqualifiedDesugaredType();
115 const CXXRecordDecl
* pRecordDecl
= pType
->getAsCXXRecordDecl();
117 const ClassTemplateSpecializationDecl
* pTemplate
= dyn_cast
<ClassTemplateSpecializationDecl
>(pRecordDecl
);
119 auto name
= pTemplate
->getQualifiedNameAsString();
120 if (name
== "VclStatusListener") {
123 bool link
= name
== "Link";
124 for(unsigned i
=0; i
<pTemplate
->getTemplateArgs().size(); ++i
) {
125 const TemplateArgument
& rArg
= pTemplate
->getTemplateArgs()[i
];
126 if (rArg
.getKind() == TemplateArgument::ArgKind::Type
&&
127 containsVclReferenceBaseSubclass(rArg
.getAsType()))
129 // OK for first template argument of tools/link.hxx Link
130 // to be a Window-derived pointer:
131 if (!link
|| i
!= 0) {
138 if (pType
->isPointerType()) {
139 QualType pointeeType
= pType
->getPointeeType();
140 return containsVclReferenceBaseSubclass(pointeeType
);
141 } else if (pType
->isArrayType()) {
142 const ArrayType
* pArrayType
= dyn_cast
<ArrayType
>(pType
);
143 QualType elementType
= pArrayType
->getElementType();
144 return containsVclReferenceBaseSubclass(elementType
);
146 return isDerivedFromVclReferenceBase(pRecordDecl
);
150 bool VCLWidgets::VisitCXXDestructorDecl(const CXXDestructorDecl
* pCXXDestructorDecl
)
152 if (ignoreLocation(pCXXDestructorDecl
)) {
155 if (!pCXXDestructorDecl
->isThisDeclarationADefinition()) {
158 const CXXRecordDecl
* pRecordDecl
= pCXXDestructorDecl
->getParent();
160 if (pRecordDecl
->getQualifiedNameAsString() == BASE_REF_COUNTED_CLASS
) {
163 // check if this class is derived from VclReferenceBase
164 if (!isDerivedFromVclReferenceBase(pRecordDecl
)) {
167 // check if we have any VclPtr<> fields
168 bool bFoundVclPtrField
= false;
169 for(auto fieldDecl
= pRecordDecl
->field_begin();
170 fieldDecl
!= pRecordDecl
->field_end(); ++fieldDecl
)
172 const RecordType
*pFieldRecordType
= fieldDecl
->getType()->getAs
<RecordType
>();
173 if (pFieldRecordType
) {
174 const CXXRecordDecl
*pFieldRecordTypeDecl
= dyn_cast
<CXXRecordDecl
>(pFieldRecordType
->getDecl());
175 if (startsWith(pFieldRecordTypeDecl
->getQualifiedNameAsString(), "VclPtr")) {
176 bFoundVclPtrField
= true;
181 // check if there is a dispose() method
182 bool bFoundDispose
= false;
183 for(auto methodDecl
= pRecordDecl
->method_begin();
184 methodDecl
!= pRecordDecl
->method_end(); ++methodDecl
)
186 if (methodDecl
->isInstance() && methodDecl
->param_size()==0 && methodDecl
->getNameAsString() == "dispose") {
187 bFoundDispose
= true;
191 const CompoundStmt
*pCompoundStatement
= dyn_cast
<CompoundStmt
>(pCXXDestructorDecl
->getBody());
192 // having an empty body and no dispose() method is fine
193 if (!bFoundVclPtrField
&& !bFoundDispose
&& pCompoundStatement
&& pCompoundStatement
->size() == 0) {
196 if (bFoundVclPtrField
&& pCompoundStatement
&& pCompoundStatement
->size() == 0) {
198 DiagnosticsEngine::Warning
,
199 BASE_REF_COUNTED_CLASS
" subclass with VclPtr field must call disposeOnce() from its destructor",
200 pCXXDestructorDecl
->getLocStart())
201 << pCXXDestructorDecl
->getSourceRange();
204 // check that the destructor for a BASE_REF_COUNTED_CLASS subclass does nothing except call into the disposeOnce() method
206 if (pCompoundStatement
) {
207 bool bFoundDisposeOnce
= false;
208 int nNumExtraStatements
= 0;
209 for (auto i
= pCompoundStatement
->body_begin();
210 i
!= pCompoundStatement
->body_end(); ++i
)
212 const CXXMemberCallExpr
*pCallExpr
= dyn_cast
<CXXMemberCallExpr
>(
215 if( const FunctionDecl
* func
= pCallExpr
->getDirectCallee()) {
216 if( func
->getNumParams() == 0 && func
->getIdentifier() != NULL
217 && ( func
->getName() == "disposeOnce" )) {
218 bFoundDisposeOnce
= true;
222 // checking for ParenExpr is a hacky way to ignore assert statements in older versions of clang (i.e. <= 3.2)
223 if (!pCallExpr
&& !dyn_cast
<ParenExpr
>(*i
))
224 nNumExtraStatements
++;
226 bOk
= bFoundDisposeOnce
&& nNumExtraStatements
== 0;
229 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
230 pCXXDestructorDecl
->getLocStart());
231 StringRef filename
= compiler
.getSourceManager().getFilename(spellingLocation
);
232 if ( !(filename
.startswith(SRCDIR
"/vcl/source/window/window.cxx"))
233 && !(filename
.startswith(SRCDIR
"/vcl/source/gdi/virdev.cxx"))
234 && !(filename
.startswith(SRCDIR
"/vcl/qa/cppunit/lifecycle.cxx")) )
237 DiagnosticsEngine::Warning
,
238 BASE_REF_COUNTED_CLASS
" subclass should have nothing in its destructor but a call to disposeOnce()",
239 pCXXDestructorDecl
->getLocStart())
240 << pCXXDestructorDecl
->getSourceRange();
246 bool VCLWidgets::VisitBinaryOperator(const BinaryOperator
* binaryOperator
)
248 if (ignoreLocation(binaryOperator
)) {
251 if ( !binaryOperator
->isAssignmentOp() ) {
254 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
255 binaryOperator
->getLocStart());
256 checkAssignmentForVclPtrToRawConversion(spellingLocation
, binaryOperator
->getLHS()->getType().getTypePtr(), binaryOperator
->getRHS());
260 // Look for places where we are accidentally assigning a returned-by-value VclPtr<T> to a T*, which generally
261 // ends up in a use-after-free.
262 void VCLWidgets::checkAssignmentForVclPtrToRawConversion(const SourceLocation
& spellingLocation
, const Type
* lhsType
, const Expr
* rhs
)
264 if (!lhsType
|| !isa
<PointerType
>(lhsType
)) {
270 StringRef filename
= compiler
.getSourceManager().getFilename(spellingLocation
);
271 if (filename
== SRCDIR
"/include/rtl/ref.hxx") {
274 const CXXRecordDecl
* pointeeClass
= lhsType
->getPointeeType()->getAsCXXRecordDecl();
275 if (!isDerivedFromVclReferenceBase(pointeeClass
)) {
279 // if we have T* on the LHS and VclPtr<T> on the RHS, we expect to see either
280 // an ImplicitCastExpr
281 // or a ExprWithCleanups and then an ImplicitCastExpr
282 if (auto implicitCastExpr
= dyn_cast
<ImplicitCastExpr
>(rhs
)) {
283 if (implicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
286 rhs
= rhs
->IgnoreCasts();
287 } else if (auto exprWithCleanups
= dyn_cast
<ExprWithCleanups
>(rhs
)) {
288 if (auto implicitCastExpr
= dyn_cast
<ImplicitCastExpr
>(exprWithCleanups
->getSubExpr())) {
289 if (implicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
292 rhs
= exprWithCleanups
->IgnoreCasts();
299 if (isa
<CXXNullPtrLiteralExpr
>(rhs
)) {
302 if (isa
<CXXThisExpr
>(rhs
)) {
306 // ignore assignments from a member field to a local variable, to avoid unnecessary refcounting traffic
307 if (auto callExpr
= dyn_cast
<CXXMemberCallExpr
>(rhs
)) {
308 if (auto calleeMemberExpr
= dyn_cast
<MemberExpr
>(callExpr
->getCallee())) {
309 if ((calleeMemberExpr
= dyn_cast
<MemberExpr
>(calleeMemberExpr
->getBase()->IgnoreImpCasts()))) {
310 if (isa
<FieldDecl
>(calleeMemberExpr
->getMemberDecl())) {
317 // ignore assignments from a local variable to a local variable, to avoid unnecessary refcounting traffic
318 if (auto callExpr
= dyn_cast
<CXXMemberCallExpr
>(rhs
)) {
319 if (auto calleeMemberExpr
= dyn_cast
<MemberExpr
>(callExpr
->getCallee())) {
320 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(calleeMemberExpr
->getBase()->IgnoreImpCasts())) {
321 if (isa
<VarDecl
>(declRefExpr
->getDecl())) {
327 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(rhs
->IgnoreImpCasts())) {
328 if (isa
<VarDecl
>(declRefExpr
->getDecl())) {
334 DiagnosticsEngine::Warning
,
335 "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",
336 rhs
->getSourceRange().getBegin())
337 << rhs
->getSourceRange();
340 bool VCLWidgets::VisitVarDecl(const VarDecl
* pVarDecl
) {
341 if (ignoreLocation(pVarDecl
)) {
344 if (isa
<ParmVarDecl
>(pVarDecl
)) {
347 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
348 pVarDecl
->getLocStart());
349 if (pVarDecl
->getInit()) {
350 checkAssignmentForVclPtrToRawConversion(spellingLocation
, pVarDecl
->getType().getTypePtr(), pVarDecl
->getInit());
352 StringRef aFileName
= compiler
.getSourceManager().getFilename(spellingLocation
);
353 if (aFileName
== SRCDIR
"/include/vcl/vclptr.hxx")
355 if (aFileName
== SRCDIR
"/vcl/source/window/layout.cxx")
357 // whitelist the valid things that can contain pointers.
358 // It is containing stuff like std::unique_ptr we get worried
359 if (pVarDecl
->getType()->isArrayType()) {
362 auto tc
= loplugin::TypeCheck(pVarDecl
->getType());
364 || tc
.Class("map").StdNamespace()
365 || tc
.Class("multimap").StdNamespace()
366 || tc
.Class("vector").StdNamespace()
367 || tc
.Class("list").StdNamespace()
368 || tc
.Class("mem_fun1_t").StdNamespace()
369 // registration template thing, doesn't actually allocate anything we need to care about
370 || tc
.Class("OMultiInstanceAutoRegistration").Namespace("dbp").GlobalNamespace())
374 // Apparently I should be doing some kind of lookup for a partial specialisations of std::iterator_traits<T> to see if an
375 // object is an iterator, but that sounds like too much work
376 auto t
= pVarDecl
->getType().getDesugaredType(compiler
.getASTContext());
377 std::string s
= t
.getAsString();
378 if (s
.find("iterator") != std::string::npos
379 || loplugin::TypeCheck(t
).Class("__wrap_iter").StdNamespace())
383 // 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
384 // its an ElaboratedType (whatever that is)
385 if (s
.find("pair") != std::string::npos
) {
389 if (containsVclReferenceBaseSubclass(pVarDecl
->getType())) {
391 DiagnosticsEngine::Warning
,
392 BASE_REF_COUNTED_CLASS
" subclass %0 should be wrapped in VclPtr",
393 pVarDecl
->getLocation())
394 << pVarDecl
->getType() << pVarDecl
->getSourceRange();
400 bool VCLWidgets::VisitFieldDecl(const FieldDecl
* fieldDecl
) {
401 if (ignoreLocation(fieldDecl
)) {
404 StringRef aFileName
= compiler
.getSourceManager().getFilename(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocStart()));
405 if (aFileName
== SRCDIR
"/include/vcl/vclptr.hxx")
407 if (aFileName
== SRCDIR
"/include/rtl/ref.hxx")
409 if (aFileName
== SRCDIR
"/include/o3tl/enumarray.hxx")
411 if (aFileName
== SRCDIR
"/vcl/source/window/layout.cxx")
413 if (fieldDecl
->isBitField()) {
416 const CXXRecordDecl
*pParentRecordDecl
= isa
<RecordDecl
>(fieldDecl
->getDeclContext()) ? dyn_cast
<CXXRecordDecl
>(fieldDecl
->getParent()) : nullptr;
417 if (pParentRecordDecl
&& loplugin::DeclCheck(pParentRecordDecl
).Class("VclPtr").GlobalNamespace()) {
420 if (containsVclReferenceBaseSubclass(fieldDecl
->getType())) {
421 // have to ignore this for now, nasty reverse dependency from tools->vcl
422 if (!(pParentRecordDecl
!= nullptr &&
423 (pParentRecordDecl
->getQualifiedNameAsString() == "ErrorContextImpl" ||
424 pParentRecordDecl
->getQualifiedNameAsString() == "ScHFEditPage"))) {
426 DiagnosticsEngine::Warning
,
427 BASE_REF_COUNTED_CLASS
" subclass %0 declared as a pointer member, should be wrapped in VclPtr",
428 fieldDecl
->getLocation())
429 << fieldDecl
->getType() << fieldDecl
->getSourceRange();
430 if (auto parent
= dyn_cast
<ClassTemplateSpecializationDecl
>(fieldDecl
->getParent())) {
432 DiagnosticsEngine::Note
,
433 "template field here",
434 parent
->getPointOfInstantiation());
439 const RecordType
*recordType
= fieldDecl
->getType()->getAs
<RecordType
>();
440 if (recordType
== nullptr) {
443 const CXXRecordDecl
*recordDecl
= dyn_cast
<CXXRecordDecl
>(recordType
->getDecl());
444 if (recordDecl
== nullptr) {
448 // check if this field is derived fromVclReferenceBase
449 if (isDerivedFromVclReferenceBase(recordDecl
)) {
451 DiagnosticsEngine::Warning
,
452 BASE_REF_COUNTED_CLASS
" subclass allocated as a class member, should be allocated via VclPtr",
453 fieldDecl
->getLocation())
454 << fieldDecl
->getSourceRange();
457 // If this field is a VclPtr field, then the class MUST have a dispose method
458 if (pParentRecordDecl
&& isDerivedFromVclReferenceBase(pParentRecordDecl
)
459 && startsWith(recordDecl
->getQualifiedNameAsString(), "VclPtr"))
461 bool bFoundDispose
= false;
462 for(auto methodDecl
= pParentRecordDecl
->method_begin();
463 methodDecl
!= pParentRecordDecl
->method_end(); ++methodDecl
)
465 if (methodDecl
->isInstance() && methodDecl
->param_size()==0 && methodDecl
->getNameAsString() == "dispose") {
466 bFoundDispose
= true;
470 if (!bFoundDispose
) {
472 DiagnosticsEngine::Warning
,
473 BASE_REF_COUNTED_CLASS
" subclass with a VclPtr field MUST override dispose() (and call its superclass dispose() as the last thing it does)",
474 fieldDecl
->getLocation())
475 << fieldDecl
->getSourceRange();
477 if (!pParentRecordDecl
->hasUserDeclaredDestructor()) {
479 DiagnosticsEngine::Warning
,
480 BASE_REF_COUNTED_CLASS
" subclass with a VclPtr field MUST have a user-provided destructor (that calls disposeOnce())",
481 fieldDecl
->getLocation())
482 << fieldDecl
->getSourceRange();
489 bool VCLWidgets::VisitParmVarDecl(ParmVarDecl
const * pvDecl
)
491 if (ignoreLocation(pvDecl
)) {
494 // ignore the stuff in the VclPtr template class
495 const CXXMethodDecl
*pMethodDecl
= dyn_cast
<CXXMethodDecl
>(pvDecl
->getDeclContext());
497 && pMethodDecl
->getParent()->getQualifiedNameAsString().find("VclPtr") != std::string::npos
) {
500 // we exclude this method in VclBuilder because it's so useful to have it like this
502 && pMethodDecl
->getNameAsString() == "get"
503 && (pMethodDecl
->getParent()->getQualifiedNameAsString() == "VclBuilder"
504 || pMethodDecl
->getParent()->getQualifiedNameAsString() == "VclBuilderContainer"))
512 static void findDisposeAndClearStatements(std::set
<const FieldDecl
*>& aVclPtrFields
, const Stmt
*pStmt
)
516 if (isa
<CompoundStmt
>(pStmt
)) {
517 const CompoundStmt
*pCompoundStatement
= dyn_cast
<CompoundStmt
>(pStmt
);
518 for (auto i
= pCompoundStatement
->body_begin();
519 i
!= pCompoundStatement
->body_end(); ++i
)
521 findDisposeAndClearStatements(aVclPtrFields
, *i
);
525 if (isa
<ForStmt
>(pStmt
)) {
526 findDisposeAndClearStatements(aVclPtrFields
, dyn_cast
<ForStmt
>(pStmt
)->getBody());
529 if (isa
<IfStmt
>(pStmt
)) {
530 findDisposeAndClearStatements(aVclPtrFields
, dyn_cast
<IfStmt
>(pStmt
)->getThen());
531 findDisposeAndClearStatements(aVclPtrFields
, dyn_cast
<IfStmt
>(pStmt
)->getElse());
534 if (!isa
<CallExpr
>(pStmt
)) return;
535 const CallExpr
*pCallExpr
= dyn_cast
<CallExpr
>(pStmt
);
537 if (!pCallExpr
->getDirectCallee()) return;
538 if (!isa
<CXXMethodDecl
>(pCallExpr
->getDirectCallee())) return;
539 const CXXMethodDecl
*pCalleeMethodDecl
= dyn_cast
<CXXMethodDecl
>(pCallExpr
->getDirectCallee());
540 if (pCalleeMethodDecl
->getNameAsString() != "disposeAndClear"
541 && pCalleeMethodDecl
->getNameAsString() != "clear")
544 if (!pCallExpr
->getCallee()) return;
546 if (!isa
<MemberExpr
>(pCallExpr
->getCallee())) return;
547 const MemberExpr
*pCalleeMemberExpr
= dyn_cast
<MemberExpr
>(pCallExpr
->getCallee());
549 if (!pCalleeMemberExpr
->getBase()) return;
550 if (!isa
<MemberExpr
>(pCalleeMemberExpr
->getBase())) return;
551 const MemberExpr
*pCalleeMemberExprBase
= dyn_cast
<MemberExpr
>(pCalleeMemberExpr
->getBase());
553 const FieldDecl
* xxx
= dyn_cast_or_null
<FieldDecl
>(pCalleeMemberExprBase
->getMemberDecl());
555 aVclPtrFields
.erase(xxx
);
559 bool VCLWidgets::VisitFunctionDecl( const FunctionDecl
* functionDecl
)
561 if (ignoreLocation(functionDecl
)) {
564 // ignore the stuff in the VclPtr template class
565 const CXXMethodDecl
*pMethodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
567 && pMethodDecl
->getParent()->getQualifiedNameAsString() == "VclPtr") {
570 // ignore the BASE_REF_COUNTED_CLASS::dispose() method
572 && pMethodDecl
->getParent()->getQualifiedNameAsString() == BASE_REF_COUNTED_CLASS
) {
575 if (functionDecl
->hasBody() && pMethodDecl
&& isDerivedFromVclReferenceBase(pMethodDecl
->getParent())) {
576 // check the last thing that the dispose() method does, is to call into the superclass dispose method
577 if (pMethodDecl
->getNameAsString() == "dispose") {
578 if (!isDisposeCallingSuperclassDispose(pMethodDecl
)) {
580 DiagnosticsEngine::Warning
,
581 BASE_REF_COUNTED_CLASS
" subclass dispose() function MUST call dispose() of its superclass as the last thing it does",
582 functionDecl
->getLocStart())
583 << functionDecl
->getSourceRange();
588 // check dispose method to make sure we are actually disposing all of the VclPtr fields
589 // FIXME this is not exhaustive. We should enable shouldVisitTemplateInstantiations and look deeper inside type declarations
590 if (pMethodDecl
&& pMethodDecl
->isInstance() && pMethodDecl
->getBody()
591 && pMethodDecl
->param_size()==0
592 && pMethodDecl
->getNameAsString() == "dispose"
593 && isDerivedFromVclReferenceBase(pMethodDecl
->getParent()) )
595 std::string methodParent
= pMethodDecl
->getParent()->getNameAsString();
596 if (methodParent
== "VirtualDevice" || methodParent
== "Breadcrumb")
599 std::set
<const FieldDecl
*> aVclPtrFields
;
600 for (auto i
= pMethodDecl
->getParent()->field_begin();
601 i
!= pMethodDecl
->getParent()->field_end(); ++i
)
603 auto const type
= loplugin::TypeCheck((*i
)->getType());
604 if (type
.Class("VclPtr").GlobalNamespace()) {
605 aVclPtrFields
.insert(*i
);
606 } else if (type
.Class("vector").StdNamespace()
607 || type
.Class("map").StdNamespace()
608 || type
.Class("list").StdNamespace()
609 || type
.Class("set").StdNamespace())
611 const RecordType
* recordType
= dyn_cast_or_null
<RecordType
>((*i
)->getType()->getUnqualifiedDesugaredType());
613 auto d
= dyn_cast
<ClassTemplateSpecializationDecl
>(recordType
->getDecl());
614 if (d
&& d
->getTemplateArgs().size()>0) {
615 auto const type
= loplugin::TypeCheck(d
->getTemplateArgs()[0].getAsType());
616 if (type
.Class("VclPtr").GlobalNamespace()) {
617 aVclPtrFields
.insert(*i
);
623 if (!aVclPtrFields
.empty()) {
624 findDisposeAndClearStatements( aVclPtrFields
, pMethodDecl
->getBody() );
625 if (!aVclPtrFields
.empty()) {
626 //pMethodDecl->dump();
627 std::string aMessage
= BASE_REF_COUNTED_CLASS
" subclass dispose() method does not call disposeAndClear() or clear() on the following field(s): ";
628 for(auto s
: aVclPtrFields
)
629 aMessage
+= ", " + s
->getNameAsString();
631 DiagnosticsEngine::Warning
,
633 functionDecl
->getLocStart())
634 << functionDecl
->getSourceRange();
642 bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr
*pCXXDeleteExpr
)
644 if (ignoreLocation(pCXXDeleteExpr
)) {
647 const CXXRecordDecl
*pPointee
= pCXXDeleteExpr
->getArgument()->getType()->getPointeeCXXRecordDecl();
648 if (pPointee
&& isDerivedFromVclReferenceBase(pPointee
)) {
649 SourceLocation spellingLocation
= compiler
.getSourceManager().getSpellingLoc(
650 pCXXDeleteExpr
->getLocStart());
651 StringRef filename
= compiler
.getSourceManager().getFilename(spellingLocation
);
652 if ( !(filename
.startswith(SRCDIR
"/include/vcl/vclreferencebase.hxx")))
655 DiagnosticsEngine::Warning
,
656 "calling delete on instance of " BASE_REF_COUNTED_CLASS
" subclass, must rather call disposeAndClear()",
657 pCXXDeleteExpr
->getLocStart())
658 << pCXXDeleteExpr
->getSourceRange();
661 const ImplicitCastExpr
* pImplicitCastExpr
= dyn_cast
<ImplicitCastExpr
>(pCXXDeleteExpr
->getArgument());
662 if (!pImplicitCastExpr
) {
665 if (pImplicitCastExpr
->getCastKind() != CK_UserDefinedConversion
) {
669 DiagnosticsEngine::Warning
,
670 "calling delete on instance of VclPtr, must rather call disposeAndClear()",
671 pCXXDeleteExpr
->getLocStart())
672 << pCXXDeleteExpr
->getSourceRange();
679 `-CXXMemberCallExpr 0xb06d8b0 'void'
680 `-MemberExpr 0xb06d868 '<bound member function type>' ->dispose 0x9d34880
681 `-ImplicitCastExpr 0xb06d8d8 'class SfxTabPage *' <UncheckedDerivedToBase (SfxTabPage)>
682 `-CXXThisExpr 0xb06d850 'class SfxAcceleratorConfigPage *' this
685 bool VCLWidgets::isDisposeCallingSuperclassDispose(const CXXMethodDecl
* pMethodDecl
)
687 const CompoundStmt
*pCompoundStatement
= dyn_cast
<CompoundStmt
>(pMethodDecl
->getBody());
688 if (!pCompoundStatement
) return false;
689 if (pCompoundStatement
->size() == 0) return false;
690 // find the last statement
691 const CXXMemberCallExpr
*pCallExpr
= dyn_cast
<CXXMemberCallExpr
>(*pCompoundStatement
->body_rbegin());
692 if (!pCallExpr
) return false;
693 const MemberExpr
*pMemberExpr
= dyn_cast
<MemberExpr
>(pCallExpr
->getCallee());
694 if (!pMemberExpr
) return false;
695 if (pMemberExpr
->getMemberDecl()->getNameAsString() != "dispose") return false;
696 const CXXMethodDecl
*pDirectCallee
= dyn_cast
<CXXMethodDecl
>(pCallExpr
->getDirectCallee());
697 if (!pDirectCallee
) return false;
698 /* Not working yet. Partially because sometimes the superclass does not a dispose() method, so it gets passed up the chain.
699 Need complex checking for that case.
700 if (pDirectCallee->getParent()->getTypeForDecl() != (*pMethodDecl->getParent()->bases_begin()).getType().getTypePtr()) {
702 DiagnosticsEngine::Warning,
703 "dispose() method calling wrong baseclass, calling " + pDirectCallee->getParent()->getQualifiedNameAsString() +
704 " should be calling " + (*pMethodDecl->getParent()->bases_begin()).getType().getAsString(),
705 pCallExpr->getLocStart())
706 << pCallExpr->getSourceRange();
712 bool containsVclPtr(const Type
* pType0
);
714 bool containsVclPtr(const QualType
& qType
) {
715 auto t
= qType
->getAs
<RecordType
>();
717 auto d
= dyn_cast
<ClassTemplateSpecializationDecl
>(t
->getDecl());
719 std::string
name(d
->getQualifiedNameAsString());
720 if (name
== "ScopedVclPtr" || name
== "ScopedVclPtrInstance"
721 || name
== "VclPtr" || name
== "VclPtrInstance")
727 return containsVclPtr(qType
.getTypePtr());
730 bool containsVclPtr(const Type
* pType0
) {
733 const Type
* pType
= pType0
->getUnqualifiedDesugaredType();
736 if (pType
->isPointerType()) {
738 } else if (pType
->isArrayType()) {
739 const ArrayType
* pArrayType
= dyn_cast
<ArrayType
>(pType
);
740 QualType elementType
= pArrayType
->getElementType();
741 return containsVclPtr(elementType
);
743 const CXXRecordDecl
* pRecordDecl
= pType
->getAsCXXRecordDecl();
746 std::string
name(pRecordDecl
->getQualifiedNameAsString());
747 if (name
== "ScopedVclPtr" || name
== "ScopedVclPtrInstance"
748 || name
== "VclPtr" || name
== "VclPtrInstance")
752 for(auto fieldDecl
= pRecordDecl
->field_begin();
753 fieldDecl
!= pRecordDecl
->field_end(); ++fieldDecl
)
755 const RecordType
*pFieldRecordType
= fieldDecl
->getType()->getAs
<RecordType
>();
756 if (pFieldRecordType
&& containsVclPtr(pFieldRecordType
)) {
760 for(auto baseSpecifier
= pRecordDecl
->bases_begin();
761 baseSpecifier
!= pRecordDecl
->bases_end(); ++baseSpecifier
)
763 const RecordType
*pFieldRecordType
= baseSpecifier
->getType()->getAs
<RecordType
>();
764 if (pFieldRecordType
&& containsVclPtr(pFieldRecordType
)) {
773 bool VCLWidgets::VisitCallExpr(const CallExpr
* pCallExpr
)
775 if (ignoreLocation(pCallExpr
)) {
778 FunctionDecl
const * fdecl
= pCallExpr
->getDirectCallee();
779 if (fdecl
== nullptr) {
782 std::string qname
{ fdecl
->getQualifiedNameAsString() };
783 if (qname
.find("memcpy") == std::string::npos
784 && qname
.find("bcopy") == std::string::npos
785 && qname
.find("memmove") == std::string::npos
786 && qname
.find("rtl_copy") == std::string::npos
) {
789 mbCheckingMemcpy
= true;
790 Stmt
* pStmt
= const_cast<Stmt
*>(static_cast<const Stmt
*>(pCallExpr
->getArg(0)));
792 mbCheckingMemcpy
= false;
796 bool VCLWidgets::VisitDeclRefExpr(const DeclRefExpr
* pDeclRefExpr
)
798 if (!mbCheckingMemcpy
) {
801 if (ignoreLocation(pDeclRefExpr
)) {
804 QualType pType
= pDeclRefExpr
->getDecl()->getType();
805 if (pType
->isPointerType()) {
806 pType
= pType
->getPointeeType();
808 if (!containsVclPtr(pType
)) {
812 DiagnosticsEngine::Warning
,
813 "Calling memcpy on a type which contains a VclPtr",
814 pDeclRefExpr
->getExprLoc());
818 bool VCLWidgets::VisitCXXConstructExpr( const CXXConstructExpr
* constructExpr
)
820 if (ignoreLocation(constructExpr
)) {
823 StringRef aFileName
= compiler
.getSourceManager().getFilename(compiler
.getSourceManager().getSpellingLoc(constructExpr
->getLocStart()));
824 if (aFileName
== SRCDIR
"/include/vcl/vclptr.hxx")
826 if (constructExpr
->getConstructionKind() != CXXConstructExpr::CK_Complete
) {
829 const CXXConstructorDecl
* pConstructorDecl
= constructExpr
->getConstructor();
830 const CXXRecordDecl
* recordDecl
= pConstructorDecl
->getParent();
831 if (isDerivedFromVclReferenceBase(recordDecl
)) {
833 DiagnosticsEngine::Warning
,
834 "Calling constructor of a VclReferenceBase-derived type directly; all such creation should go via VclPtr<>::Create",
835 constructExpr
->getExprLoc());
840 loplugin::Plugin::Registration
< VCLWidgets
> X("vclwidgets");
844 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */