/* -*- 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 "check.hxx" #include "compat.hxx" #include "plugin.hxx" #include "clang/AST/CXXInheritance.h" /** If you have: class Foo : public css::foo::XBaa { }; Then XBaa has acquire and release methods inherited from XInterface. These are hard lifecycle controls. If you see another class: class Baz { Foo aFooMember; }; this is a bug =) since aFooMember assumes heap allocated lifecycle and not delete on last 'release'. TODO check that things that extend SvRefBase are managed by SvRef TODO fix the slideshow::internal::SlideView class (mentioned below) */ namespace { class RefCounting: public RecursiveASTVisitor, public loplugin::Plugin { public: explicit RefCounting(InstantiationData const & data): Plugin(data) {} virtual void run() override { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } bool VisitFieldDecl(const FieldDecl *); bool VisitVarDecl(const VarDecl *); bool VisitFunctionDecl(const FunctionDecl *); // Creation of temporaries with one argument are represented by // CXXFunctionalCastExpr, while any other number of arguments are // represented by CXXTemporaryObjectExpr: bool VisitCXXTemporaryObjectExpr(CXXTemporaryObjectExpr const * expr) { return visitTemporaryObjectExpr(expr); } bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr const * expr) { return visitTemporaryObjectExpr(expr); } bool WalkUpFromObjCIvarDecl(ObjCIvarDecl * decl) { // Don't recurse into WalkUpFromFieldDecl, as VisitFieldDecl calls // FieldDecl::getParent, which triggers an assertion at least with // current trunk towards Clang 3.7 when the FieldDecl is actually an // ObjCIvarDecl. return VisitObjCIvarDecl(decl); } private: void checkUnoReference(QualType qt, const Decl* decl, const std::string& rParentName, const std::string& rDeclName); bool visitTemporaryObjectExpr(Expr const * expr); }; bool BaseCheckNotSubclass(const CXXRecordDecl *BaseDefinition, void *p) { if (!BaseDefinition) return true; const char *pString = static_cast(p); if (BaseDefinition->getQualifiedNameAsString() == pString) { return false; } return true; } bool isDerivedFrom(const CXXRecordDecl *decl, const char *pString) { if (!decl) return false; if (decl->getQualifiedNameAsString() == pString) return true; if (!decl->hasDefinition()) { return false; } if (!compat::forallBases( *decl, #if CLANG_VERSION < 30800 BaseCheckNotSubclass, #else [pString](const CXXRecordDecl *BaseDefinition) -> bool { return BaseCheckNotSubclass( BaseDefinition, const_cast(pString)); }, #endif static_cast(const_cast(pString)), true)) { return true; } return false; } bool containsXInterfaceSubclass(const Type* pType0); bool containsXInterfaceSubclass(const QualType& qType) { return containsXInterfaceSubclass(qType.getTypePtr()); } bool containsXInterfaceSubclass(const Type* pType0) { if (!pType0) return false; const Type* pType = pType0->getUnqualifiedDesugaredType(); if (!pType) return false; const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl(); if (pRecordDecl) { pRecordDecl = pRecordDecl->getCanonicalDecl(); // these classes override acquire/release and forwards to its parent if (isDerivedFrom(pRecordDecl, "ListenerMultiplexerBase")) { // module UnoTools return false; } if (isDerivedFrom(pRecordDecl, "toolkit::GridEventForwarder")) { // module toolkit return false; } if (isDerivedFrom(pRecordDecl, "OWeakSubObject")) { // module svx return false; } if (isDerivedFrom(pRecordDecl, "dbaui::OSbaWeakSubObject")) { // module dbaccess return false; } // The actual problem child is SlideView, of which this is the parent. // Everything in the hierarchy above this wants to be managed via boost::shared_ptr if (isDerivedFrom(pRecordDecl, "slideshow::internal::UnoView")) { // module slideshow return false; } // FIXME This class has private operator new, and I cannot figure out how it can be dynamically instantiated if (isDerivedFrom(pRecordDecl, "XPropertyList")) { // module svx return false; } } if (pRecordDecl) { const ClassTemplateSpecializationDecl* pTemplate = dyn_cast(pRecordDecl); if (pTemplate) { // Probably good templates: loplugin::DeclCheck dc(pTemplate); if ((dc.Struct("FindUnoInstanceHint").AnonymousNamespace() .GlobalNamespace()) || (dc.Class("OMultiInstanceAutoRegistration").Namespace("abp") .GlobalNamespace()) || (dc.Class("Reference").Namespace("uno").Namespace("star") .Namespace("sun").Namespace("com").GlobalNamespace()) || (dc.Class("WeakReference").Namespace("uno").Namespace("star") .Namespace("sun").Namespace("com").GlobalNamespace()) || (dc.Class("Sequence").Namespace("uno").Namespace("star") .Namespace("sun").Namespace("com").GlobalNamespace()) || (dc.Class("WeakCppRef").Namespace("accessibility") .GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("dba") .GlobalNamespace()) || (dc.Class("OMultiInstanceAutoRegistration").Namespace("dbp") .GlobalNamespace()) || (dc.Class("OMultiInstanceAutoRegistration") .Namespace("dbaui").GlobalNamespace()) || (dc.Class("OMultiInstanceAutoRegistration") .Namespace("dbaxml").GlobalNamespace()) || (dc.Struct("ReferenceEqual").Namespace("io_acceptor") .GlobalNamespace()) || (dc.Struct("ReferenceHash").Namespace("io_acceptor") .GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("comphelper") .GlobalNamespace()) || (dc.Struct("OInterfaceCompare").Namespace("comphelper") .GlobalNamespace()) || dc.Class("WeakBag").Namespace("comphelper").GlobalNamespace() || (dc.Struct("class_").Namespace("service_decl") .Namespace("comphelper").GlobalNamespace()) || (dc.Struct("vba_service_class_").Namespace("service_decl") .Namespace("comphelper").GlobalNamespace()) || (dc.Struct("inheritingClass_").Namespace("service_decl") .Namespace("comphelper").GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("module") .Namespace("comphelper").GlobalNamespace()) || (dc.Class("mem_fun1_t").Namespace("comphelper") .GlobalNamespace()) || (dc.Class("OSimpleListenerContainer").Namespace("comphelper") .GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("dbmm") .GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("pcr") .GlobalNamespace()) || (dc.Class("ComponentMethodGuard").Namespace("logging") .GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("logging") .GlobalNamespace()) || dc.Class("Reference").Namespace("rtl").GlobalNamespace() || (dc.Class("OAutoRegistration").Namespace("sdbtools") .GlobalNamespace()) || (dc.Struct("ReferenceEqual").Namespace("stoc_connector") .GlobalNamespace()) || (dc.Struct("ReferenceHash").Namespace("stoc_connector") .GlobalNamespace()) || dc.Class("mem_fun_t").StdNamespace() || dc.Class("mem_fun1_t").StdNamespace() || dc.Class("SwIterator").GlobalNamespace() || (dc.Class("InitGuard").Namespace("toolkit") .GlobalNamespace()) || (dc.Class("SharedUNOComponent").Namespace("utl") .GlobalNamespace()) || (dc.Class("OAutoRegistration").Namespace("utl") .GlobalNamespace()) || (dc.Class("DeleteUnoReferenceOnDeinit").Namespace("vcl") .GlobalNamespace()) || (dc.Struct("OInterfaceCompare").Namespace("xmloff") .GlobalNamespace())) { return false; } } } if (pType->isPointerType()) { // ignore return false; } else if (pType->isArrayType()) { const ArrayType* pArrayType = dyn_cast(pType); QualType elementType = pArrayType->getElementType(); return containsXInterfaceSubclass(elementType); } else { return isDerivedFrom(pRecordDecl, "com::sun::star::uno::XInterface"); } } bool containsSvRefBaseSubclass(const Type* pType0) { if (!pType0) return false; const Type* pType = pType0->getUnqualifiedDesugaredType(); if (!pType) return false; const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl(); if (pRecordDecl) { pRecordDecl = pRecordDecl->getCanonicalDecl(); } if (pRecordDecl) { const ClassTemplateSpecializationDecl* pTemplate = dyn_cast(pRecordDecl); if (pTemplate) { std::string aName = pTemplate->getQualifiedNameAsString(); if (aName == "tools::SvRef") return false; for(unsigned i=0; igetTemplateArgs().size(); ++i) { const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i]; if (rArg.getKind() == TemplateArgument::ArgKind::Type && containsSvRefBaseSubclass(rArg.getAsType().getTypePtr())) { return true; } } } } if (pType->isPointerType()) { // ignore return false; } else if (pType->isArrayType()) { const ArrayType* pArrayType = dyn_cast(pType); QualType elementType = pArrayType->getElementType(); return containsSvRefBaseSubclass(elementType.getTypePtr()); } else { return isDerivedFrom(pRecordDecl, "tools::SvRefBase"); } } bool containsSalhelperReferenceObjectSubclass(const Type* pType0) { if (!pType0) return false; const Type* pType = pType0->getUnqualifiedDesugaredType(); if (!pType) return false; const CXXRecordDecl* pRecordDecl = pType->getAsCXXRecordDecl(); if (pRecordDecl) { pRecordDecl = pRecordDecl->getCanonicalDecl(); } if (pRecordDecl) { const ClassTemplateSpecializationDecl* pTemplate = dyn_cast(pRecordDecl); if (pTemplate) { std::string aName = pTemplate->getQualifiedNameAsString(); if (aName == "rtl::Reference" || aName == "store::OStoreHandle") return false; for(unsigned i=0; igetTemplateArgs().size(); ++i) { const TemplateArgument& rArg = pTemplate->getTemplateArgs()[i]; if (rArg.getKind() == TemplateArgument::ArgKind::Type && containsSalhelperReferenceObjectSubclass(rArg.getAsType().getTypePtr())) { return true; } } } } if (pType->isPointerType()) { // ignore return false; } else if (pType->isArrayType()) { const ArrayType* pArrayType = dyn_cast(pType); QualType elementType = pArrayType->getElementType(); return containsSalhelperReferenceObjectSubclass(elementType.getTypePtr()); } else { return isDerivedFrom(pRecordDecl, "salhelper::SimpleReferenceObject"); } } static bool containsStaticTypeMethod(const CXXRecordDecl* x) { for (auto it = x->method_begin(); it != x->method_end(); ++it) { auto i = *it; if ( !i->isStatic() ) continue; auto ident = i->getIdentifier(); if ( ident && ident->isStr("static_type") ) { return true; } } return false; } void RefCounting::checkUnoReference(QualType qt, const Decl* decl, const std::string& rParentName, const std::string& rDeclName) { if (loplugin::TypeCheck(qt).Class("Reference").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace()) { const CXXRecordDecl* pRecordDecl = qt->getAsCXXRecordDecl(); const ClassTemplateSpecializationDecl* pTemplate = dyn_cast(pRecordDecl); const TemplateArgument& rArg = pTemplate->getTemplateArgs()[0]; const CXXRecordDecl* templateParam = rArg.getAsType()->getAsCXXRecordDecl()->getDefinition(); if (templateParam && !containsStaticTypeMethod(templateParam)) { report( DiagnosticsEngine::Warning, "uno::Reference " + rDeclName + " with template parameter that does not contain ::static_type() " + qt.getAsString() + ", parent is " + rParentName + ", should probably be using rtl::Reference instead", decl->getLocation()) << decl->getSourceRange(); } } } bool RefCounting::visitTemporaryObjectExpr(Expr const * expr) { if (ignoreLocation(expr)) { return true; } auto t = expr->getType(); if (containsSvRefBaseSubclass(t.getTypePtr())) { report( DiagnosticsEngine::Warning, ("Temporary object of SvRefBase subclass %0 being directly stack" " managed, should be managed via tools::SvRef"), expr->getLocStart()) << t.getUnqualifiedType() << expr->getSourceRange(); } else if (containsSalhelperReferenceObjectSubclass(t.getTypePtr())) { report( DiagnosticsEngine::Warning, ("Temporary object of salhelper::SimpleReferenceObject subclass %0" " being directly stack managed, should be managed via" " rtl::Reference"), expr->getLocStart()) << t.getUnqualifiedType() << expr->getSourceRange(); } else if (containsXInterfaceSubclass(t)) { report( DiagnosticsEngine::Warning, ("Temporary object of css::uno::XInterface subclass %0 being" " directly stack managed, should be managed via" " css::uno::Reference"), expr->getLocStart()) << t.getUnqualifiedType() << expr->getSourceRange(); } return true; } bool RefCounting::VisitFieldDecl(const FieldDecl * fieldDecl) { if (ignoreLocation(fieldDecl)) { return true; } if (fieldDecl->isBitField()) { return true; } std::string aParentName = fieldDecl->getParent()->getQualifiedNameAsString(); if (containsSvRefBaseSubclass(fieldDecl->getType().getTypePtr())) { report( DiagnosticsEngine::Warning, "SvRefBase subclass being directly heap managed, should be managed via tools::SvRef, " + fieldDecl->getType().getAsString() + ", parent is " + aParentName, fieldDecl->getLocation()) << fieldDecl->getSourceRange(); } if (containsSalhelperReferenceObjectSubclass(fieldDecl->getType().getTypePtr())) { report( DiagnosticsEngine::Warning, "salhelper::SimpleReferenceObject subclass being directly heap managed, should be managed via rtl::Reference, " + fieldDecl->getType().getAsString() + ", parent is " + aParentName, fieldDecl->getLocation()) << fieldDecl->getSourceRange(); } if ( aParentName == "com::sun::star::uno::BaseReference" || aParentName == "cppu::detail::element_alias" // this is playing some kind of game to avoid circular references || aParentName == "ucbhelper::ResultSetDataSupplier") { return true; } if (containsXInterfaceSubclass(fieldDecl->getType())) { report( DiagnosticsEngine::Warning, "XInterface subclass being directly heap managed, should be managed via uno::Reference, " + fieldDecl->getType().getAsString() + ", parent is " + aParentName, fieldDecl->getLocation()) << fieldDecl->getSourceRange(); } checkUnoReference(fieldDecl->getType(), fieldDecl, aParentName, "field"); return true; } bool RefCounting::VisitVarDecl(const VarDecl * varDecl) { if (ignoreLocation(varDecl)) { return true; } if (!isa(varDecl)) { if (containsSvRefBaseSubclass(varDecl->getType().getTypePtr())) { report( DiagnosticsEngine::Warning, "SvRefBase subclass being directly stack managed, should be managed via tools::SvRef, " + varDecl->getType().getAsString(), varDecl->getLocation()) << varDecl->getSourceRange(); } if (containsSalhelperReferenceObjectSubclass(varDecl->getType().getTypePtr())) { StringRef name { compiler.getSourceManager().getFilename(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())) }; // this is playing games that it believes is safe if (name == SRCDIR "/stoc/source/security/permissions.cxx") return true; report( DiagnosticsEngine::Warning, "salhelper::SimpleReferenceObject subclass being directly stack managed, should be managed via rtl::Reference, " + varDecl->getType().getAsString(), varDecl->getLocation()) << varDecl->getSourceRange(); } if (containsXInterfaceSubclass(varDecl->getType())) { report( DiagnosticsEngine::Warning, "XInterface subclass being directly stack managed, should be managed via uno::Reference, " + varDecl->getType().getAsString(), varDecl->getLocation()) << varDecl->getSourceRange(); } } checkUnoReference(varDecl->getType(), varDecl, "", "var"); return true; } bool RefCounting::VisitFunctionDecl(const FunctionDecl * functionDecl) { if (ignoreLocation(functionDecl)) { return true; } // only consider base declarations, not overriden ones, or we warn on methods that // are overriding stuff from external libraries const CXXMethodDecl * methodDecl = dyn_cast(functionDecl); if (methodDecl && methodDecl->size_overridden_methods() > 0) { return true; } checkUnoReference(compat::getReturnType(*functionDecl), functionDecl, "", "return"); return true; } loplugin::Plugin::Registration< RefCounting > X("refcounting"); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */