bef96f7a7b
The function is not just about a spelling location. Change-Id: I96e9e9ef7e27a9763397b4b86473c1c30d0e3eeb Reviewed-on: https://gerrit.libreoffice.org/80381 Tested-by: Jenkins Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
531 lines
20 KiB
C++
531 lines
20 KiB
C++
/* -*- 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 <cassert>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <set>
|
|
|
|
#include <clang/AST/CXXInheritance.h>
|
|
#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<UnnecessaryOverride>
|
|
{
|
|
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<CXXConstructorDecl>(methodDecl)) {
|
|
return true;
|
|
}
|
|
|
|
StringRef aFileName = getFilenameOfLocation(
|
|
compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(methodDecl)));
|
|
|
|
if (isa<CXXDestructorDecl>(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<RecordType>();
|
|
if (baseRecordType)
|
|
{
|
|
const CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(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<CompoundStmt>(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<CompoundStmt>(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<Expr>(*compoundStmt->body_begin())) {
|
|
callExpr = dyn_cast<CXXMemberCallExpr>(e->IgnoreImplicit()->IgnoreParens());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto returnStmt = dyn_cast<ReturnStmt>(*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<DeclStmt>(*bodyIt);
|
|
if (!declStmt || !declStmt->isSingleDecl())
|
|
return true;
|
|
auto varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
|
|
++bodyIt;
|
|
auto returnStmt = dyn_cast<ReturnStmt>(*bodyIt);
|
|
if (!varDecl || !returnStmt)
|
|
return true;
|
|
Expr const * retValue = returnStmt->getRetValue()->IgnoreParenImpCasts();
|
|
if (auto exprWithCleanups = dyn_cast<ExprWithCleanups>(retValue))
|
|
retValue = exprWithCleanups->getSubExpr()->IgnoreParenImpCasts();
|
|
if (auto constructExpr = dyn_cast<CXXConstructExpr>(retValue)) {
|
|
if (constructExpr->getNumArgs() == 1)
|
|
retValue = constructExpr->getArg(0)->IgnoreParenImpCasts();
|
|
}
|
|
if (!isa<DeclRefExpr>(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<CXXThisExpr>(expr1);
|
|
if (!expr2)
|
|
return true;
|
|
for (unsigned i = 0; i<callExpr->getNumArgs(); ++i) {
|
|
auto e = callExpr->getArg(i)->IgnoreImplicit();
|
|
if (auto const e1 = dyn_cast<CXXConstructExpr>(e)) {
|
|
if (e1->getConstructor()->isCopyOrMoveConstructor() && e1->getNumArgs() == 1) {
|
|
e = e1->getArg(0)->IgnoreImpCasts();
|
|
}
|
|
}
|
|
const DeclRefExpr * declRefExpr = dyn_cast<DeclRefExpr>(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; i<methodDecl->param_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<CXXConstructExpr>(returnExpr)) {
|
|
if (ctorExpr->getNumArgs() == 1) {
|
|
auto tempExpr1 = ctorExpr->getArg(0)->IgnoreImplicit();
|
|
if (auto tempExpr2 = dyn_cast<CXXBindTemporaryExpr>(tempExpr1))
|
|
{
|
|
returnExpr = tempExpr2->getSubExpr();
|
|
}
|
|
else if (auto tempExpr2 = dyn_cast<CXXMemberCallExpr>(tempExpr1))
|
|
{
|
|
returnExpr = tempExpr2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return dyn_cast<CXXMemberCallExpr>(returnExpr->IgnoreParenImpCasts());
|
|
}
|
|
|
|
loplugin::Plugin::Registration< UnnecessaryOverride > unnecessaryoverride("unnecessaryoverride", true);
|
|
|
|
}
|
|
|
|
#endif // LO_CLANG_SHARED_PLUGINS
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|