/* -*- 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/. */ #ifndef LO_CLANG_SHARED_PLUGINS #include #include #include #include #include #include #include "plugin.hxx" /** look for methods where all they do is call their superclass method */ namespace { bool hasMultipleBaseInstances_( CXXRecordDecl const * derived, CXXRecordDecl const * canonicBase, bool & hasAsNonVirtualBase, bool & hasAsVirtualBase) { for (auto i = derived->bases_begin(); i != derived->bases_end(); ++i) { auto const cls = i->getType()->getAsCXXRecordDecl(); if (cls == nullptr) { assert(i->getType()->isDependentType()); // Conservatively assume "yes" for dependent bases: return true; } if (cls->getCanonicalDecl() == canonicBase) { if (i->isVirtual()) { if (hasAsNonVirtualBase) { return true; } hasAsVirtualBase = true; } else { if (hasAsNonVirtualBase || hasAsVirtualBase) { return true; } hasAsNonVirtualBase = true; } } else if (hasMultipleBaseInstances_( cls, canonicBase, hasAsNonVirtualBase, hasAsVirtualBase)) { return true; } } return false; } bool hasMultipleBaseInstances( CXXRecordDecl const * derived, CXXRecordDecl const * base) { bool nonVirt = false; bool virt = false; return hasMultipleBaseInstances_( derived, base->getCanonicalDecl(), nonVirt, virt); } class UnnecessaryOverride: public loplugin::FilteringPlugin { public: explicit UnnecessaryOverride(loplugin::InstantiationData const & data): FilteringPlugin(data) {} virtual bool preRun() override { // ignore some files with problematic macros StringRef fn(handler.getMainFileName()); if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/framework/factories/ChildWindowPane.cxx")) return false; if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Date.cxx")) return false; if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Time.cxx")) return false; if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/hyperdlg.cxx")) return false; if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/rubydialog.cxx")) return false; if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/")) return false; if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/view/spelldialog.cxx")) return false; if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/dlg/SpellDialogChildWindow.cxx")) return false; // HAVE_ODBC_ADMINISTRATION if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dsselect.cxx")) return false; return true; } virtual void run() override { if( preRun()) TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); } bool VisitCXXMethodDecl(const CXXMethodDecl *); private: const CXXMethodDecl * findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl *); bool BaseCheckCallback(const CXXRecordDecl *BaseDefinition); CXXMemberCallExpr const * extractCallExpr(Expr const *); }; bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl) { if (ignoreLocation(methodDecl->getCanonicalDecl())) { return true; } if (isa(methodDecl)) { return true; } StringRef aFileName = getFilenameOfLocation( compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(methodDecl))); if (isa(methodDecl) && !isInUnoIncludeFile(methodDecl)) { // the code is this method is only __compiled__ if OSL_DEBUG_LEVEL > 1 if (loplugin::isSamePathname(aFileName, SRCDIR "/tools/source/stream/strmunx.cxx")) return true; // Warn about unnecessarily user-declared destructors. // A destructor is deemed unnecessary if: // * it is public; // * its class is only defined in the .cxx file (i.e., the virtual // destructor is neither used to control the place of vtable // emission, nor is its definition depending on types that may still // be incomplete); // or // the destructor is inline, the class definition is complete, // and the class has no superclasses // * it either does not have an explicit exception specification, or has // a non-dependent explicit exception specification that is compatible // with a non-dependent exception specification the destructor would // have if it did not have an explicit one (TODO); // * it is either defined as defaulted or with an empty body. // Removing the user-declared destructor may cause the class to get an // implicitly declared move constructor and/or move assignment operator; // that is considered acceptable: If any subobject cannot be moved, the // implicitly declared function will be defined as deleted (which is in // practice not much different from not having it declared), and // otherwise offering movability is likely even an improvement over not // offering it due to a "pointless" user-declared destructor. // Similarly, removing the user-declared destructor may cause the // implicit definition of a copy constructor and/or copy assignment // operator to change from being an obsolete feature to being a standard // feature. That difference is not taken into account here. auto cls = methodDecl->getParent(); if (methodDecl->getAccess() != AS_public) { return true; } if (!compiler.getSourceManager().isInMainFile( methodDecl->getCanonicalDecl()->getLocation()) && !( methodDecl->isInlined())) { return true; } // if it's virtual, but it has a base-class with a non-virtual destructor if (methodDecl->isVirtual()) { bool baseWithVirtualDtor = false; for (auto baseSpecifier = cls->bases_begin(); baseSpecifier != cls->bases_end(); ++baseSpecifier) { const RecordType* baseRecordType = baseSpecifier->getType()->getAs(); if (baseRecordType) { const CXXRecordDecl* baseRecordDecl = dyn_cast(baseRecordType->getDecl()); if (baseRecordDecl && baseRecordDecl->getDestructor() && baseRecordDecl->getDestructor()->isVirtual()) { baseWithVirtualDtor = true; break; } } } if (!baseWithVirtualDtor) { return true; } } // corner case if (methodDecl->isInlined() && compiler.getSourceManager().isInMainFile(methodDecl->getLocation()) && !compiler.getSourceManager().isInMainFile(methodDecl->getCanonicalDecl()->getLocation())) { return true; } if (!methodDecl->isExplicitlyDefaulted()) { if (!methodDecl->doesThisDeclarationHaveABody() || methodDecl->isLateTemplateParsed()) { return true; } auto stmt = dyn_cast(methodDecl->getBody()); if (stmt == nullptr || stmt->size() != 0) { return true; } } //TODO: exception specification if (!(cls->hasUserDeclaredCopyConstructor() || cls->hasUserDeclaredCopyAssignment() || cls->hasUserDeclaredMoveConstructor() || cls->hasUserDeclaredMoveAssignment())) { } if ((cls->needsImplicitMoveConstructor() && !(cls->hasUserDeclaredCopyConstructor() || cls->hasUserDeclaredCopyAssignment() || cls->hasUserDeclaredMoveAssignment())) || (cls->needsImplicitMoveAssignment() && !(cls->hasUserDeclaredCopyConstructor() || cls->hasUserDeclaredCopyAssignment() || cls->hasUserDeclaredMoveConstructor()))) { report(DiagnosticsEngine::Fatal, "TODO", methodDecl->getLocation()); return true; } report( DiagnosticsEngine::Warning, "unnecessary user-declared destructor", methodDecl->getLocation()) << methodDecl->getSourceRange(); auto cd = methodDecl->getCanonicalDecl(); if (cd->getLocation() != methodDecl->getLocation()) { report(DiagnosticsEngine::Note, "declared here", cd->getLocation()) << cd->getSourceRange(); } return true; } if (!methodDecl->doesThisDeclarationHaveABody() || methodDecl->isLateTemplateParsed()) { return true; } // If overriding more than one base member function, or one base member // function that is available in multiple (non-virtual) base class // instances, then this is a disambiguating override: if (methodDecl->isVirtual()) { if (methodDecl->size_overridden_methods() != 1) { return true; } if (hasMultipleBaseInstances( methodDecl->getParent(), (*methodDecl->begin_overridden_methods())->getParent())) { return true; } } const CXXMethodDecl* overriddenMethodDecl = findOverriddenOrSimilarMethodInSuperclasses(methodDecl); if (!overriddenMethodDecl) { return true; } // Check for differences in default parameters: unsigned const numParams = methodDecl->getNumParams(); assert(overriddenMethodDecl->getNumParams() == numParams); for (unsigned i = 0; i != numParams; ++i) { if (checkIdenticalDefaultArguments( methodDecl->getParamDecl(i)->getDefaultArg(), overriddenMethodDecl->getParamDecl(i)->getDefaultArg()) != IdenticalDefaultArgumentsResult::Yes) { return true; } } if (methodDecl->getReturnType().getCanonicalType() != overriddenMethodDecl->getReturnType().getCanonicalType()) { return true; } //TODO: check for identical exception specifications const CompoundStmt* compoundStmt = dyn_cast(methodDecl->getBody()); if (!compoundStmt || compoundStmt->size() > 2) return true; const CXXMemberCallExpr* callExpr = nullptr; if (compoundStmt->size() == 1) { if (methodDecl->getReturnType().getCanonicalType()->isVoidType()) { if (auto const e = dyn_cast(*compoundStmt->body_begin())) { callExpr = dyn_cast(e->IgnoreImplicit()->IgnoreParens()); } } else { auto returnStmt = dyn_cast(*compoundStmt->body_begin()); if (returnStmt == nullptr) { return true; } auto returnExpr = returnStmt->getRetValue(); if (returnExpr == nullptr) { return true; } callExpr = extractCallExpr(returnExpr); } } else if (!methodDecl->getReturnType().getCanonicalType()->isVoidType()) { /** handle constructions like bool foo() { bool ret = Base::foo(); return ret; } */ auto bodyIt = compoundStmt->body_begin(); auto declStmt = dyn_cast(*bodyIt); if (!declStmt || !declStmt->isSingleDecl()) return true; auto varDecl = dyn_cast(declStmt->getSingleDecl()); ++bodyIt; auto returnStmt = dyn_cast(*bodyIt); if (!varDecl || !returnStmt) return true; Expr const * retValue = returnStmt->getRetValue()->IgnoreParenImpCasts(); if (auto exprWithCleanups = dyn_cast(retValue)) retValue = exprWithCleanups->getSubExpr()->IgnoreParenImpCasts(); if (auto constructExpr = dyn_cast(retValue)) { if (constructExpr->getNumArgs() == 1) retValue = constructExpr->getArg(0)->IgnoreParenImpCasts(); } if (!isa(retValue)) return true; callExpr = extractCallExpr(varDecl->getInit()); } if (!callExpr || callExpr->getMethodDecl() != overriddenMethodDecl) return true; const Expr* expr1 = callExpr->getImplicitObjectArgument()->IgnoreImpCasts(); if (!expr1) return true; const CXXThisExpr* expr2 = dyn_cast_or_null(expr1); if (!expr2) return true; for (unsigned i = 0; igetNumArgs(); ++i) { auto e = callExpr->getArg(i)->IgnoreImplicit(); if (auto const e1 = dyn_cast(e)) { if (e1->getConstructor()->isCopyOrMoveConstructor() && e1->getNumArgs() == 1) { e = e1->getArg(0)->IgnoreImpCasts(); } } const DeclRefExpr * declRefExpr = dyn_cast(e); if (!declRefExpr || declRefExpr->getDecl() != methodDecl->getParamDecl(i)) return true; } const CXXMethodDecl* pOther = nullptr; if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation()) pOther = methodDecl->getCanonicalDecl(); if (pOther) { StringRef aFileName = getFilenameOfLocation( compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(pOther))); // SFX_DECL_CHILDWINDOW_WITHID macro if (loplugin::isSamePathname(aFileName, SRCDIR "/include/sfx2/childwin.hxx")) return true; } report( DiagnosticsEngine::Warning, "%0%1 function just calls %2 parent", methodDecl->getLocation()) << methodDecl->getAccess() << (methodDecl->isVirtual() ? " virtual" : "") << overriddenMethodDecl->getAccess() << methodDecl->getSourceRange(); if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation()) { const CXXMethodDecl* pOther = methodDecl->getCanonicalDecl(); report( DiagnosticsEngine::Note, "method declaration here", pOther->getLocation()) << pOther->getSourceRange(); } return true; } const CXXMethodDecl* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl* methodDecl) { if (methodDecl->isVirtual()) { return *methodDecl->begin_overridden_methods(); } if (!methodDecl->getDeclName().isIdentifier()) { return nullptr; } const CXXMethodDecl* similarMethod = nullptr; CXXBasePath similarBasePath; auto BaseMatchesCallback = [&](const CXXBaseSpecifier *cxxBaseSpecifier, CXXBasePath& path) { if (cxxBaseSpecifier->getAccessSpecifier() != AS_public && cxxBaseSpecifier->getAccessSpecifier() != AS_protected) return false; if (!cxxBaseSpecifier->getType().getTypePtr()) return false; const CXXRecordDecl* baseCXXRecordDecl = cxxBaseSpecifier->getType()->getAsCXXRecordDecl(); if (!baseCXXRecordDecl) return false; if (baseCXXRecordDecl->isInvalidDecl()) return false; for (const CXXMethodDecl* baseMethod : baseCXXRecordDecl->methods()) { auto effectiveBaseMethodAccess = baseMethod->getAccess(); if (effectiveBaseMethodAccess == AS_public && path.Access == AS_protected) effectiveBaseMethodAccess = AS_protected; if (effectiveBaseMethodAccess != methodDecl->getAccess()) continue; if (!baseMethod->getDeclName().isIdentifier() || methodDecl->getName() != baseMethod->getName()) { continue; } if (methodDecl->isStatic() != baseMethod->isStatic() || methodDecl->isConst() != baseMethod->isConst() || methodDecl->isVolatile() != baseMethod->isVolatile() || (methodDecl->getRefQualifier() != baseMethod->getRefQualifier()) || methodDecl->isVariadic() != baseMethod->isVariadic()) { continue; } if (methodDecl->getReturnType().getCanonicalType() != baseMethod->getReturnType().getCanonicalType()) { continue; } if (methodDecl->getNumParams() != baseMethod->getNumParams()) continue; bool bParamsMatch = true; for (unsigned i=0; iparam_size(); ++i) { if (methodDecl->parameters()[i]->getType() != baseMethod->parameters()[i]->getType()) { bParamsMatch = false; break; } } if (bParamsMatch) { // if we have already found a method directly below us in the inheritance hierarchy, just ignore this one auto Compare = [&](CXXBasePathElement const & lhs, CXXBasePathElement const & rhs) { return lhs.Class == rhs.Class; }; if (similarMethod && similarBasePath.size() < path.size() && std::equal(similarBasePath.begin(), similarBasePath.end(), path.begin(), Compare)) break; if (similarMethod) return true; // short circuit the process similarMethod = baseMethod; similarBasePath = path; } } return false; }; CXXBasePaths aPaths; if (methodDecl->getParent()->lookupInBases(BaseMatchesCallback, aPaths)) return nullptr; return similarMethod; } CXXMemberCallExpr const * UnnecessaryOverride::extractCallExpr(Expr const *returnExpr) { returnExpr = returnExpr->IgnoreImplicit(); // In something like // // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery( // const rtl::OUString& sql) // throw(SQLException, RuntimeException, std::exception) // { // return OCommonStatement::executeQuery( sql ); // } // // look down through all the // // ReturnStmt // `-ExprWithCleanups // `-CXXConstructExpr // `-MaterializeTemporaryExpr // `-ImplicitCastExpr // `-CXXBindTemporaryExpr // `-CXXMemberCallExpr // // where the fact that the overriding and overridden function have identical // return types makes us confident that all we need to check here is whether // there's an (arbitrary, one-argument) CXXConstructorExpr and // CXXBindTemporaryExpr in between: if (auto ctorExpr = dyn_cast(returnExpr)) { if (ctorExpr->getNumArgs() == 1) { auto tempExpr1 = ctorExpr->getArg(0)->IgnoreImplicit(); if (auto tempExpr2 = dyn_cast(tempExpr1)) { returnExpr = tempExpr2->getSubExpr(); } else if (auto tempExpr2 = dyn_cast(tempExpr1)) { returnExpr = tempExpr2; } } } return dyn_cast(returnExpr->IgnoreParenImpCasts()); } loplugin::Plugin::Registration< UnnecessaryOverride > unnecessaryoverride("unnecessaryoverride", true); } #endif // LO_CLANG_SHARED_PLUGINS /* vim:set shiftwidth=4 softtabstop=4 expandtab: */