/* -*- 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 *); bool VisitCallExpr(const CallExpr *); bool VisitDeclRefExpr(const DeclRefExpr* pDeclRefExpr); bool VisitCXXConstructExpr( const CXXConstructExpr* expr ); private: bool isDisposeCallingSuperclassDispose(const CXXMethodDecl* pMethodDecl); bool mbCheckingMemcpy = false; }; 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() == "OutputDevice") { return false; } return true; } bool isDerivedFromWindow(const CXXRecordDecl *decl) { if (!decl) return false; if (decl->getQualifiedNameAsString() == "OutputDevice") 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) { auto t = qType->getAs(); if (t != nullptr) { auto d = dyn_cast(t->getDecl()); if (d != nullptr) { std::string name(d->getQualifiedNameAsString()); if (name == "ScopedVclPtr" || name == "ScopedVclPtrInstance" || name == "VclPtr" || name == "VclPtrInstance") { 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) { bool link = pTemplate->getQualifiedNameAsString() == "Link"; for(unsigned i=0; igetTemplateArgs().size(); ++i) { const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i]; if (rArg.getKind() == TemplateArgument::ArgKind::Type && containsWindowSubclass(rArg.getAsType())) { // OK for first template argument of tools/link.hxx Link // to be a Window-derived pointer: if (!link || i != 0) { 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 OutputDevice class if (pRecordDecl->getQualifiedNameAsString() == "OutputDevice") { return true; } // check if this class is derived from Window if (!isDerivedFromWindow(pRecordDecl)) { return true; } bool foundVclPtrField = false; for(auto fieldDecl = pRecordDecl->field_begin(); fieldDecl != pRecordDecl->field_end(); ++fieldDecl) { 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->method_begin(); methodDecl != pRecordDecl->method_end(); ++methodDecl) { 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, "OutputDevice subclass with VclPtr field must call disposeOnce() from its destructor.", pCXXDestructorDecl->getLocStart()) << pCXXDestructorDecl->getSourceRange(); return true; } // check that the destructor for a OutputDevice subclass does nothing except call into the disposeOnce() method bool ok = false; if (pCompoundStatement) { bool bFoundDisposeOnce = false; int nNumExtraStatements = 0; for(auto const * x : pCompoundStatement->body()) { const CXXMemberCallExpr *pCallExpr = dyn_cast(x); if (pCallExpr) { if( const FunctionDecl* func = pCallExpr->getDirectCallee()) { if( func->getNumParams() == 0 && func->getIdentifier() != NULL && ( func->getName() == "disposeOnce" )) { bFoundDisposeOnce = true; } } } // checking for ParenExpr is a hacky way to ignore assert statements in older versions of clang (i.e. <= 3.2) if (!pCallExpr && !dyn_cast(x)) nNumExtraStatements++; } ok = bFoundDisposeOnce && nNumExtraStatements == 0; } if (!ok) { SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc( pCXXDestructorDecl->getLocStart()); StringRef filename = compiler.getSourceManager().getFilename(spellingLocation); if ( !(filename.startswith(SRCDIR "/vcl/source/window/window.cxx")) && !(filename.startswith(SRCDIR "/vcl/source/gdi/virdev.cxx")) ) { report( DiagnosticsEngine::Warning, "OutputDevice subclass should have nothing in its destructor but a call to disposeOnce().", pCXXDestructorDecl->getLocStart()) << pCXXDestructorDecl->getSourceRange(); } } return true; } bool VCLWidgets::VisitVarDecl(const VarDecl * pVarDecl) { if (ignoreLocation(pVarDecl)) { return true; } if ( isa(pVarDecl) || pVarDecl->isLocalVarDecl() ) { return true; } if ( !startsWith(pVarDecl->getType().getAsString(), "std::vector") && !startsWith(pVarDecl->getType().getAsString(), "std::map") && !startsWith(pVarDecl->getType().getAsString(), "std::map") && !startsWith(pVarDecl->getType().getAsString(), "::std::vector") && !startsWith(pVarDecl->getType().getAsString(), "::std::vector