/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "plugin.hxx" #include "compat.hxx" #include "clang/AST/CXXInheritance.h" // Final goal: Checker for VCL widget references. Makes sure that VCL Window subclasses are properly referenced counted and dispose()'ed. // // But at the moment it just finds subclasses of Window which are not heap-allocated // // TODO do I need to check for local and static variables, too ? // TODO when we have a dispose() method, verify that the dispose() methods releases all of the Window references // TODO when we have a dispose() method, verify that it calls the super-class dispose() method at some point. namespace { class VCLWidgets: public RecursiveASTVisitor, public loplugin::Plugin { public: explicit VCLWidgets(InstantiationData const & data): Plugin(data) {} virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } bool VisitVarDecl(const VarDecl *); bool VisitFieldDecl(const FieldDecl *); bool VisitParmVarDecl(const ParmVarDecl *); bool VisitFunctionDecl(const FunctionDecl *); bool VisitCXXDestructorDecl(const CXXDestructorDecl *); bool VisitCXXDeleteExpr(const CXXDeleteExpr *); private: bool isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl); }; static bool startsWith(const std::string& s, const char* other) { return s.compare(0, strlen(other), other) == 0; } bool BaseCheckNotWindowSubclass(const CXXRecordDecl *BaseDefinition, void *) { if (BaseDefinition && BaseDefinition->getQualifiedNameAsString().compare("vcl::Window") == 0) { return false; } return true; } bool isDerivedFromWindow(const CXXRecordDecl *decl) { if (!decl) return false; if (decl->getQualifiedNameAsString() == "vcl::Window") return true; if (!decl->hasDefinition()) { return false; } if (// not sure what hasAnyDependentBases() does, // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1 !decl->hasAnyDependentBases() && !decl->forallBases(BaseCheckNotWindowSubclass, nullptr, true)) { return true; } return false; } bool containsWindowSubclass(const Type* pType0); bool containsWindowSubclass(const QualType& qType) { if (startsWith(qType.getAsString(), "VclPtr")) return false; if (startsWith(qType.getAsString(), "class VclPtr")) return false; if (startsWith(qType.getAsString(), "const class VclPtr")) return false; return containsWindowSubclass(qType.getTypePtr()); } bool containsWindowSubclass(const Type* pType0) { if (!pType0) return false; const Type* pType = pType0->getUnqualifiedDesugaredType(); if (!pType) return false; const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl(); if (pRecordDecl) { const ClassTemplateSpecializationDecl* pTemplate = dyn_cast(pRecordDecl); if (pTemplate) { for(unsigned i=0; igetTemplateArgs().size(); ++i) { const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i]; if (rArg.getKind() == TemplateArgument::ArgKind::Type && containsWindowSubclass(rArg.getAsType())) { return true; } } } } if (pType->isPointerType()) { QualType pointeeType = pType->getPointeeType(); return containsWindowSubclass(pointeeType); } else if (pType->isArrayType()) { const ArrayType* pArrayType = dyn_cast(pType); QualType elementType = pArrayType->getElementType(); return containsWindowSubclass(elementType); } else { return isDerivedFromWindow(pRecordDecl); } } bool VCLWidgets::VisitCXXDestructorDecl(const CXXDestructorDecl* pCXXDestructorDecl) { if (ignoreLocation(pCXXDestructorDecl)) { return true; } if (!pCXXDestructorDecl->isThisDeclarationADefinition()) { return true; } const CXXRecordDecl * pRecordDecl = pCXXDestructorDecl->getParent(); // ignore vcl::Window class if (pRecordDecl->getQualifiedNameAsString().compare("vcl::Window") == 0) { return true; } // check if this class is derived from Window if (!isDerivedFromWindow(pRecordDecl)) { return true; } bool foundVclPtrField = false; for(auto fieldDecl : pRecordDecl->fields()) { const RecordType *pFieldRecordType = fieldDecl->getType()->getAs(); if (pFieldRecordType) { const CXXRecordDecl *pFieldRecordTypeDecl = dyn_cast(pFieldRecordType->getDecl()); if (startsWith(pFieldRecordTypeDecl->getQualifiedNameAsString(), "VclPtr")) { foundVclPtrField = true; break; } } } bool foundDispose = false; for(auto methodDecl : pRecordDecl->methods()) { if (methodDecl->isInstance() && methodDecl->param_size()==0 && methodDecl->getNameAsString() == "dispose") { foundDispose = true; break; } } const CompoundStmt *pCompoundStatement = dyn_cast(pCXXDestructorDecl->getBody()); // having an empty body and no dispose() method is fine if (!foundVclPtrField && !foundDispose && pCompoundStatement && pCompoundStatement->size() == 0) { return true; } if (foundVclPtrField && pCompoundStatement && pCompoundStatement->size() == 0) { report( DiagnosticsEngine::Warning, "vcl::Window subclass with VclPtr field must call dispose() from it's destructor.", pCXXDestructorDecl->getLocStart()) << pCXXDestructorDecl->getSourceRange(); return true; } // check that the destructor for a vcl::Window subclass does nothing except call into the dispose() method bool ok = false; if (pCompoundStatement && pCompoundStatement->size() == 1) { const CXXMemberCallExpr *pCallExpr = dyn_cast(*pCompoundStatement->body_begin()); if (pCallExpr) { if( const FunctionDecl* func = pCallExpr->getDirectCallee()) { if( func->getNumParams() == 0 && func->getIdentifier() != NULL && ( func->getName() == "disposeOnce" )) { ok = true; } } } } if (!ok) { report( DiagnosticsEngine::Warning, "vcl::Window subclass should have nothing in it's destructor but a call to disposeOnce().", pCXXDestructorDecl->getLocStart()) << pCXXDestructorDecl->getSourceRange(); return true; } return true; } bool VCLWidgets::VisitVarDecl(const VarDecl * pVarDecl) { if (ignoreLocation(pVarDecl)) { return true; } const RecordType *recordType = pVarDecl->getType()->getAs(); if (recordType == nullptr) { return true; } const CXXRecordDecl *recordDecl = dyn_cast(recordType->getDecl()); if (recordDecl == nullptr) { return true; } // check if this field is derived from Window if (isDerivedFromWindow(recordDecl)) { report( DiagnosticsEngine::Warning, "vcl::Window subclass allocated on stack, should be allocated via VclPtr or via *.", pVarDecl->getLocation()) << pVarDecl->getSourceRange(); } return true; } bool VCLWidgets::VisitFieldDecl(const FieldDecl * fieldDecl) { if (ignoreLocation(fieldDecl)) { return true; } if (fieldDecl->isBitField()) { return true; } if (containsWindowSubclass(fieldDecl->getType())) { report( DiagnosticsEngine::Warning, "vcl::Window subclass declared as a pointer field, should be wrapped in VclPtr." + fieldDecl->getType().getAsString(), fieldDecl->getLocation()) << fieldDecl->getSourceRange(); return true; } const RecordType *recordType = fieldDecl->getType()->getAs(); if (recordType == nullptr) { return true; } const CXXRecordDecl *recordDecl = dyn_cast(recordType->getDecl()); if (recordDecl == nullptr) { return true; } // check if this field is derived from Window if (isDerivedFromWindow(recordDecl)) { report( DiagnosticsEngine::Warning, "vcl::Window subclass allocated as a class member, should be allocated via VclPtr.", fieldDecl->getLocation()) << fieldDecl->getSourceRange(); } // If this field is a VclPtr field, then the class MUST have a dispose method const CXXRecordDecl *pParentRecordDecl = dyn_cast(fieldDecl->getParent()); if (pParentRecordDecl && isDerivedFromWindow(pParentRecordDecl) && startsWith(recordDecl->getQualifiedNameAsString(), "VclPtr")) { bool foundDispose = false; for(auto methodDecl : pParentRecordDecl->methods()) { if (methodDecl->isInstance() && methodDecl->param_size()==0 && methodDecl->getNameAsString() == "dispose") { foundDispose = true; break; } } if (!foundDispose) { report( DiagnosticsEngine::Warning, "vcl::Window subclass with a VclPtr field MUST have a dispose() method.", fieldDecl->getLocation()) << fieldDecl->getSourceRange(); } } return true; } bool VCLWidgets::VisitParmVarDecl(ParmVarDecl const * pvDecl) { if (ignoreLocation(pvDecl)) { return true; } // ignore the stuff in the VclPtr template class const CXXMethodDecl *pMethodDecl = dyn_cast(pvDecl->getDeclContext()); if (pMethodDecl && pMethodDecl->getParent()->getQualifiedNameAsString().find("VclPtr") != std::string::npos) { return true; } // we exclude this method in VclBuilder because it's so useful to have it like this if (pMethodDecl && pMethodDecl->getNameAsString() == "get" && (pMethodDecl->getParent()->getQualifiedNameAsString() == "VclBuilder" || pMethodDecl->getParent()->getQualifiedNameAsString() == "VclBuilderContainer")) { return true; } if (!pvDecl->getType()->isReferenceType() && pvDecl->getType().getAsString().find("VclPtr") != std::string::npos) { report( DiagnosticsEngine::Warning, "vcl::Window subclass passed as a VclPtr parameter, should be passed as a raw pointer.", pvDecl->getCanonicalDecl()->getLocation()) << pvDecl->getCanonicalDecl()->getSourceRange(); } return true; } static void findDisposeAndClearStatements2(std::vector& aVclPtrFields, const Stmt *pStmt); static void findDisposeAndClearStatements(std::vector& aVclPtrFields, const CompoundStmt *pCompoundStatement) { for(const Stmt* pStmt : pCompoundStatement->body()) { findDisposeAndClearStatements2(aVclPtrFields, pStmt); } } static void findDisposeAndClearStatements2(std::vector& aVclPtrFields, const Stmt *pStmt) { if (isa(pStmt)) { findDisposeAndClearStatements(aVclPtrFields, dyn_cast(pStmt)); return; } if (isa(pStmt)) { const CompoundStmt *pBody = dyn_cast(dyn_cast(pStmt)->getBody()); if (pBody) findDisposeAndClearStatements(aVclPtrFields, pBody); return; } if (!isa(pStmt)) return; const CallExpr *pCallExpr = dyn_cast(pStmt); if (!pCallExpr->getDirectCallee()) return; if (!isa(pCallExpr->getDirectCallee())) return; const CXXMethodDecl *pCalleeMethodDecl = dyn_cast(pCallExpr->getDirectCallee()); if (pCalleeMethodDecl->getNameAsString() != "disposeAndClear") return; if (!pCallExpr->getCallee()) return; if (!isa(pCallExpr->getCallee())) return; const MemberExpr *pCalleeMemberExpr = dyn_cast(pCallExpr->getCallee()); if (!pCalleeMemberExpr->getBase()) return; if (!isa(pCalleeMemberExpr->getBase())) return; const MemberExpr *pCalleeMemberExprBase = dyn_cast(pCalleeMemberExpr->getBase()); std::string xxx = pCalleeMemberExprBase->getMemberDecl()->getNameAsString(); aVclPtrFields.erase(std::remove(aVclPtrFields.begin(), aVclPtrFields.end(), xxx), aVclPtrFields.end()); } bool VCLWidgets::VisitFunctionDecl( const FunctionDecl* functionDecl ) { if (ignoreLocation(functionDecl)) { return true; } // ignore the stuff in the VclPtr template class const CXXMethodDecl *pMethodDecl = dyn_cast(functionDecl); if (pMethodDecl && pMethodDecl->getParent()->getQualifiedNameAsString() == "VclPtr") { return true; } // ignore the vcl::Window::dispose() method if (pMethodDecl && pMethodDecl->getParent()->getQualifiedNameAsString() == "vcl::Window") { return true; } QualType t1 { compat::getReturnType(*functionDecl) }; if (t1.getAsString().find("VclPtr") == 0) { report( DiagnosticsEngine::Warning, "VclPtr declared as a return type from a method/function, should be passed as a raw pointer.", functionDecl->getLocation()) << functionDecl->getSourceRange(); } if (functionDecl->hasBody() && pMethodDecl && isDerivedFromWindow(pMethodDecl->getParent())) { // check the last thing that the dispose() method does, is to call into the superclass dispose method if (pMethodDecl->getNameAsString() == "dispose") { if (!isDisposeCallingSuperclassDispose(pMethodDecl)) { report( DiagnosticsEngine::Warning, "vcl::Window subclass dispose() method MUST call it's superclass dispose() as the last thing it does", functionDecl->getLocStart()) << functionDecl->getSourceRange(); } } } // check dispose method to make sure we are actually disposing all of the VclPtr fields if (pMethodDecl && pMethodDecl->isInstance() && pMethodDecl->getBody() && pMethodDecl->param_size()==0 && pMethodDecl->getNameAsString() == "dispose" && isDerivedFromWindow(pMethodDecl->getParent()) ) { // exclude a couple of methods with hard-to-parse code if (pMethodDecl->getQualifiedNameAsString() == "SvxRubyDialog::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SvxPersonalizationTabPage::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SelectPersonaDialog::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "MappingDialog_Impl::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "BibGeneralPage::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SwCreateAuthEntryDlg_Impl::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SwTableColumnPage::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SwAssignFieldsControl::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "ScOptSolverDlg::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "ScPivotFilterDlg::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SmToolBoxWindow::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "dbaui::DlgOrderCrit::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "SvxStyleBox_Impl::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "dbaui::OAppDetailPageHelper::dispose") return true; if (pMethodDecl->getQualifiedNameAsString() == "sd::CustomAnimationCreateDialog::dispose") return true; std::vector aVclPtrFields; for(auto fieldDecl : pMethodDecl->getParent()->fields()) { if (startsWith(fieldDecl->getType().getAsString(), "VclPtr")) { aVclPtrFields.push_back(fieldDecl->getNameAsString()); } } if (!aVclPtrFields.empty()) { if (pMethodDecl->getBody() && isa(pMethodDecl->getBody())) findDisposeAndClearStatements( aVclPtrFields, dyn_cast(pMethodDecl->getBody()) ); if (!aVclPtrFields.empty()) { //pMethodDecl->dump(); std::string aMessage = "vcl::Window subclass dispose() method does not call disposeAndClear() on the following field(s) "; for(auto s : aVclPtrFields) aMessage += "\n " + s + ".clear();"; report( DiagnosticsEngine::Warning, aMessage, functionDecl->getLocStart()) << functionDecl->getSourceRange(); } } } return true; } bool VCLWidgets::VisitCXXDeleteExpr(const CXXDeleteExpr *pCXXDeleteExpr) { if (ignoreLocation(pCXXDeleteExpr)) { return true; } const ImplicitCastExpr* pImplicitCastExpr = dyn_cast(pCXXDeleteExpr->getArgument()); if (!pImplicitCastExpr) { return true; } if (pImplicitCastExpr->getCastKind() != CK_UserDefinedConversion) { return true; } report( DiagnosticsEngine::Warning, "calling delete on instance of VclPtr, must rather call disposeAndClear()", pCXXDeleteExpr->getLocStart()) << pCXXDeleteExpr->getSourceRange(); return true; } /** The AST looks like: `-CXXMemberCallExpr 0xb06d8b0 'void' `-MemberExpr 0xb06d868 '' ->dispose 0x9d34880 `-ImplicitCastExpr 0xb06d8d8 'class SfxTabPage *' `-CXXThisExpr 0xb06d850 'class SfxAcceleratorConfigPage *' this */ bool VCLWidgets::isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl) { const CompoundStmt *pCompoundStatement = dyn_cast(pMethodDecl->getBody()); if (!pCompoundStatement) return false; if (pCompoundStatement->size() == 0) return false; // find the last statement const CXXMemberCallExpr *pCallExpr = dyn_cast(*pCompoundStatement->body_rbegin()); if (!pCallExpr) return false; const MemberExpr *pMemberExpr = dyn_cast(pCallExpr->getCallee()); if (!pMemberExpr) return false; if (pMemberExpr->getMemberDecl()->getNameAsString() != "dispose") return false; const CXXMethodDecl *pDirectCallee = dyn_cast(pCallExpr->getDirectCallee()); if (!pDirectCallee) return false; /* Not working yet. Partially because sometimes the superclass does not a dispose() method, so it gets passed up the chain. Need complex checking for that case. if (pDirectCallee->getParent()->getTypeForDecl() != (*pMethodDecl->getParent()->bases_begin()).getType().getTypePtr()) { report( DiagnosticsEngine::Warning, "dispose() method calling wrong baseclass, calling " + pDirectCallee->getParent()->getQualifiedNameAsString() + " should be calling " + (*pMethodDecl->getParent()->bases_begin()).getType().getAsString(), pCallExpr->getLocStart()) << pCallExpr->getSourceRange(); return false; }*/ return true; } loplugin::Plugin::Registration< VCLWidgets > X("vclwidgets"); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */