1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #ifndef LO_CLANG_SHARED_PLUGINS
18 #include <clang/AST/CXXInheritance.h>
19 #include "config_clang.h"
24 look for methods where all they do is call their superclass method
29 bool hasMultipleBaseInstances_(
30 CXXRecordDecl
const * derived
, CXXRecordDecl
const * canonicBase
,
31 bool & hasAsNonVirtualBase
, bool & hasAsVirtualBase
)
33 for (auto i
= derived
->bases_begin(); i
!= derived
->bases_end(); ++i
) {
34 auto const cls
= i
->getType()->getAsCXXRecordDecl();
36 assert(i
->getType()->isDependentType());
37 // Conservatively assume "yes" for dependent bases:
40 if (cls
->getCanonicalDecl() == canonicBase
) {
42 if (hasAsNonVirtualBase
) {
45 hasAsVirtualBase
= true;
47 if (hasAsNonVirtualBase
|| hasAsVirtualBase
) {
50 hasAsNonVirtualBase
= true;
52 } else if (hasMultipleBaseInstances_(
53 cls
, canonicBase
, hasAsNonVirtualBase
, hasAsVirtualBase
))
61 bool hasMultipleBaseInstances(
62 CXXRecordDecl
const * derived
, CXXRecordDecl
const * base
)
66 return hasMultipleBaseInstances_(
67 derived
, base
->getCanonicalDecl(), nonVirt
, virt
);
70 class UnnecessaryOverride
:
71 public loplugin::FilteringPlugin
<UnnecessaryOverride
>
74 explicit UnnecessaryOverride(loplugin::InstantiationData
const & data
): FilteringPlugin(data
) {}
76 virtual bool preRun() override
78 // ignore some files with problematic macros
79 StringRef
fn(handler
.getMainFileName());
80 if (loplugin::isSamePathname(fn
, SRCDIR
"/sd/source/ui/framework/factories/ChildWindowPane.cxx"))
82 if (loplugin::isSamePathname(fn
, SRCDIR
"/forms/source/component/Date.cxx"))
84 if (loplugin::isSamePathname(fn
, SRCDIR
"/forms/source/component/Time.cxx"))
86 if (loplugin::isSamePathname(fn
, SRCDIR
"/svx/source/dialog/hyperdlg.cxx"))
88 if (loplugin::isSamePathname(fn
, SRCDIR
"/svx/source/dialog/rubydialog.cxx"))
90 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/canvas/"))
92 if (loplugin::isSamePathname(fn
, SRCDIR
"/sc/source/ui/view/spelldialog.cxx"))
94 if (loplugin::isSamePathname(fn
, SRCDIR
"/sd/source/ui/dlg/SpellDialogChildWindow.cxx"))
96 // HAVE_ODBC_ADMINISTRATION
97 if (loplugin::isSamePathname(fn
, SRCDIR
"/dbaccess/source/ui/dlg/dsselect.cxx"))
99 if (loplugin::isSamePathname(fn
, SRCDIR
"/unotools/source/streaming/streamhelper.cxx"))
104 virtual void run() override
107 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
110 bool VisitCXXMethodDecl(const CXXMethodDecl
*);
113 const CXXMethodDecl
* findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl
*);
114 bool BaseCheckCallback(const CXXRecordDecl
*BaseDefinition
);
115 CXXMemberCallExpr
const * extractCallExpr(Expr
const *);
118 bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl
* methodDecl
)
120 if (ignoreLocation(methodDecl
->getCanonicalDecl())) {
123 if (isa
<CXXConstructorDecl
>(methodDecl
)) {
127 StringRef aFileName
= getFilenameOfLocation(
128 compiler
.getSourceManager().getSpellingLoc(methodDecl
->getBeginLoc()));
130 if (isa
<CXXDestructorDecl
>(methodDecl
)
131 && !isInUnoIncludeFile(methodDecl
))
133 // the code is this method is only __compiled__ if OSL_DEBUG_LEVEL > 1
134 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/tools/source/stream/strmunx.cxx"))
137 // Warn about unnecessarily user-declared destructors.
138 // A destructor is deemed unnecessary if:
140 // * its class is only defined in the .cxx file (i.e., the virtual
141 // destructor is neither used to control the place of vtable
142 // emission, nor is its definition depending on types that may still
145 // the destructor is inline, the class definition is complete,
146 // and the class has no superclasses
147 // * it either does not have an explicit exception specification, or has
148 // a non-dependent explicit exception specification that is compatible
149 // with a non-dependent exception specification the destructor would
150 // have if it did not have an explicit one (TODO);
151 // * it is either defined as defaulted or with an empty body.
152 // Removing the user-declared destructor may cause the class to get an
153 // implicitly declared move constructor and/or move assignment operator;
154 // that is considered acceptable: If any subobject cannot be moved, the
155 // implicitly declared function will be defined as deleted (which is in
156 // practice not much different from not having it declared), and
157 // otherwise offering movability is likely even an improvement over not
158 // offering it due to a "pointless" user-declared destructor.
159 // Similarly, removing the user-declared destructor may cause the
160 // implicit definition of a copy constructor and/or copy assignment
161 // operator to change from being an obsolete feature to being a standard
162 // feature. That difference is not taken into account here.
163 auto cls
= methodDecl
->getParent();
164 if (methodDecl
->getAccess() != AS_public
)
168 if (!compiler
.getSourceManager().isInMainFile(
169 methodDecl
->getCanonicalDecl()->getLocation())
170 && !( methodDecl
->isInlined()))
174 // if it's virtual, but it has a base-class with a non-virtual destructor
175 if (methodDecl
->isVirtual())
177 bool baseWithVirtualDtor
= false;
178 for (auto baseSpecifier
= cls
->bases_begin(); baseSpecifier
!= cls
->bases_end(); ++baseSpecifier
)
180 const RecordType
* baseRecordType
= baseSpecifier
->getType()->getAs
<RecordType
>();
183 const CXXRecordDecl
* baseRecordDecl
= dyn_cast
<CXXRecordDecl
>(baseRecordType
->getDecl());
184 if (baseRecordDecl
&& baseRecordDecl
->getDestructor()
185 && baseRecordDecl
->getDestructor()->isVirtual())
187 baseWithVirtualDtor
= true;
192 if (!baseWithVirtualDtor
)
197 if (methodDecl
->isExplicitlyDefaulted()) {
198 if (methodDecl
->getPreviousDecl() != nullptr) {
202 if (!methodDecl
->doesThisDeclarationHaveABody()
203 || methodDecl
->isLateTemplateParsed())
207 auto stmt
= dyn_cast
<CompoundStmt
>(methodDecl
->getBody());
208 if (stmt
== nullptr || stmt
->size() != 0) {
212 //TODO: exception specification
213 if (!(cls
->hasUserDeclaredCopyConstructor()
214 || cls
->hasUserDeclaredCopyAssignment()
215 || cls
->hasUserDeclaredMoveConstructor()
216 || cls
->hasUserDeclaredMoveAssignment()))
219 if ((cls
->needsImplicitMoveConstructor()
220 && !(cls
->hasUserDeclaredCopyConstructor()
221 || cls
->hasUserDeclaredCopyAssignment()
222 || cls
->hasUserDeclaredMoveAssignment()))
223 || (cls
->needsImplicitMoveAssignment()
224 && !(cls
->hasUserDeclaredCopyConstructor()
225 || cls
->hasUserDeclaredCopyAssignment()
226 || cls
->hasUserDeclaredMoveConstructor())))
228 report(DiagnosticsEngine::Fatal
, "TODO", methodDecl
->getLocation());
232 DiagnosticsEngine::Warning
, "unnecessary user-declared destructor",
233 methodDecl
->getLocation())
234 << methodDecl
->getSourceRange();
235 auto cd
= methodDecl
->getCanonicalDecl();
236 if (cd
->getLocation() != methodDecl
->getLocation()) {
237 report(DiagnosticsEngine::Note
, "declared here", cd
->getLocation())
238 << cd
->getSourceRange();
243 if (!methodDecl
->doesThisDeclarationHaveABody()
244 || methodDecl
->isLateTemplateParsed())
249 // If overriding more than one base member function, or one base member
250 // function that is available in multiple (non-virtual) base class
251 // instances, then this is a disambiguating override:
252 if (methodDecl
->isVirtual()) {
253 if (methodDecl
->size_overridden_methods() != 1)
257 if (hasMultipleBaseInstances(
258 methodDecl
->getParent(),
259 (*methodDecl
->begin_overridden_methods())->getParent()))
265 const CXXMethodDecl
* overriddenMethodDecl
= findOverriddenOrSimilarMethodInSuperclasses(methodDecl
);
266 if (!overriddenMethodDecl
) {
270 // Check for differences in default parameters:
271 unsigned const numParams
= methodDecl
->getNumParams();
272 assert(overriddenMethodDecl
->getNumParams() == numParams
);
273 for (unsigned i
= 0; i
!= numParams
; ++i
) {
274 if (checkIdenticalDefaultArguments(
275 methodDecl
->getParamDecl(i
)->getDefaultArg(),
276 overriddenMethodDecl
->getParamDecl(i
)->getDefaultArg())
277 != IdenticalDefaultArgumentsResult::Yes
)
283 if (methodDecl
->getReturnType().getCanonicalType()
284 != overriddenMethodDecl
->getReturnType().getCanonicalType())
289 //TODO: check for identical exception specifications
291 const CompoundStmt
* compoundStmt
= dyn_cast
<CompoundStmt
>(methodDecl
->getBody());
292 if (!compoundStmt
|| compoundStmt
->size() > 2)
295 const CXXMemberCallExpr
* callExpr
= nullptr;
296 if (compoundStmt
->size() == 1)
298 if (methodDecl
->getReturnType().getCanonicalType()->isVoidType())
300 if (auto const e
= dyn_cast
<Expr
>(*compoundStmt
->body_begin())) {
301 callExpr
= dyn_cast
<CXXMemberCallExpr
>(e
->IgnoreImplicit()->IgnoreParens());
306 auto returnStmt
= dyn_cast
<ReturnStmt
>(*compoundStmt
->body_begin());
307 if (returnStmt
== nullptr) {
310 auto returnExpr
= returnStmt
->getRetValue();
311 if (returnExpr
== nullptr) {
314 callExpr
= extractCallExpr(returnExpr
);
317 else if (!methodDecl
->getReturnType().getCanonicalType()->isVoidType())
319 /** handle constructions like
321 bool ret = Base::foo();
325 auto bodyIt
= compoundStmt
->body_begin();
326 if (bodyIt
== compoundStmt
->body_end()) {
329 auto declStmt
= dyn_cast
<DeclStmt
>(*bodyIt
);
330 if (!declStmt
|| !declStmt
->isSingleDecl())
332 auto varDecl
= dyn_cast
<VarDecl
>(declStmt
->getSingleDecl());
334 auto returnStmt
= dyn_cast
<ReturnStmt
>(*bodyIt
);
335 if (!varDecl
|| !returnStmt
)
337 Expr
const * retValue
= returnStmt
->getRetValue()->IgnoreParenImpCasts();
338 if (auto exprWithCleanups
= dyn_cast
<ExprWithCleanups
>(retValue
))
339 retValue
= exprWithCleanups
->getSubExpr()->IgnoreParenImpCasts();
340 if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(retValue
)) {
341 if (constructExpr
->getNumArgs() == 1)
342 retValue
= constructExpr
->getArg(0)->IgnoreParenImpCasts();
344 if (!isa
<DeclRefExpr
>(retValue
))
346 callExpr
= extractCallExpr(varDecl
->getInit());
349 if (!callExpr
|| callExpr
->getMethodDecl() != overriddenMethodDecl
)
351 const Expr
* expr1
= callExpr
->getImplicitObjectArgument()->IgnoreImpCasts();
354 const CXXThisExpr
* expr2
= dyn_cast_or_null
<CXXThisExpr
>(expr1
);
357 for (unsigned i
= 0; i
<callExpr
->getNumArgs(); ++i
) {
358 auto e
= callExpr
->getArg(i
)->IgnoreImplicit();
359 if (auto const e1
= dyn_cast
<CXXConstructExpr
>(e
)) {
360 if (e1
->getConstructor()->isCopyOrMoveConstructor() && e1
->getNumArgs() == 1) {
361 e
= e1
->getArg(0)->IgnoreImpCasts();
364 const DeclRefExpr
* declRefExpr
= dyn_cast
<DeclRefExpr
>(e
);
365 if (!declRefExpr
|| declRefExpr
->getDecl() != methodDecl
->getParamDecl(i
))
369 const CXXMethodDecl
* pOther
= nullptr;
370 if (methodDecl
->getCanonicalDecl()->getLocation() != methodDecl
->getLocation())
371 pOther
= methodDecl
->getCanonicalDecl();
374 StringRef aFileName
= getFilenameOfLocation(
375 compiler
.getSourceManager().getSpellingLoc(pOther
->getBeginLoc()));
376 // SFX_DECL_CHILDWINDOW_WITHID macro
377 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/sfx2/childwin.hxx"))
381 if (containsPreprocessingConditionalInclusion(methodDecl
->getBody()->getSourceRange())) {
386 DiagnosticsEngine::Warning
, "%0%1 function just calls %2 parent",
387 methodDecl
->getLocation())
388 << methodDecl
->getAccess()
389 << (methodDecl
->isVirtual() ? " virtual" : "")
390 << overriddenMethodDecl
->getAccess()
391 << methodDecl
->getSourceRange();
392 if (methodDecl
->getCanonicalDecl()->getLocation() != methodDecl
->getLocation()) {
393 const CXXMethodDecl
* pOther
= methodDecl
->getCanonicalDecl();
395 DiagnosticsEngine::Note
,
396 "method declaration here",
397 pOther
->getLocation())
398 << pOther
->getSourceRange();
403 const CXXMethodDecl
* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl
* methodDecl
)
405 if (methodDecl
->isVirtual()) {
406 return *methodDecl
->begin_overridden_methods();
408 if (!methodDecl
->getDeclName().isIdentifier()) {
412 const CXXMethodDecl
* similarMethod
= nullptr;
413 CXXBasePath similarBasePath
;
415 auto BaseMatchesCallback
= [&](const CXXBaseSpecifier
*cxxBaseSpecifier
, CXXBasePath
& path
)
417 if (cxxBaseSpecifier
->getAccessSpecifier() != AS_public
&& cxxBaseSpecifier
->getAccessSpecifier() != AS_protected
)
419 if (!cxxBaseSpecifier
->getType().getTypePtr())
421 const CXXRecordDecl
* baseCXXRecordDecl
= cxxBaseSpecifier
->getType()->getAsCXXRecordDecl();
422 if (!baseCXXRecordDecl
)
424 if (baseCXXRecordDecl
->isInvalidDecl())
426 for (const CXXMethodDecl
* baseMethod
: baseCXXRecordDecl
->methods())
428 auto effectiveBaseMethodAccess
= baseMethod
->getAccess();
429 if (effectiveBaseMethodAccess
== AS_public
&& path
.Access
== AS_protected
)
430 effectiveBaseMethodAccess
= AS_protected
;
431 if (effectiveBaseMethodAccess
!= methodDecl
->getAccess())
433 if (!baseMethod
->getDeclName().isIdentifier() || methodDecl
->getName() != baseMethod
->getName()) {
436 if (methodDecl
->isStatic() != baseMethod
->isStatic()
437 || methodDecl
->isConst() != baseMethod
->isConst()
438 || methodDecl
->isVolatile() != baseMethod
->isVolatile()
439 || (methodDecl
->getRefQualifier()
440 != baseMethod
->getRefQualifier())
441 || methodDecl
->isVariadic() != baseMethod
->isVariadic())
445 if (methodDecl
->getReturnType().getCanonicalType()
446 != baseMethod
->getReturnType().getCanonicalType())
450 if (methodDecl
->getNumParams() != baseMethod
->getNumParams())
452 bool bParamsMatch
= true;
453 for (unsigned i
=0; i
<methodDecl
->param_size(); ++i
)
455 if (methodDecl
->parameters()[i
]->getType() != baseMethod
->parameters()[i
]->getType())
457 bParamsMatch
= false;
463 // if we have already found a method directly below us in the inheritance hierarchy, just ignore this one
464 auto Compare
= [&](CXXBasePathElement
const & lhs
, CXXBasePathElement
const & rhs
)
466 return lhs
.Class
== rhs
.Class
;
469 && similarBasePath
.size() < path
.size()
470 && std::equal(similarBasePath
.begin(), similarBasePath
.end(),
471 path
.begin(), Compare
))
474 return true; // short circuit the process
475 similarMethod
= baseMethod
;
476 similarBasePath
= path
;
483 if (methodDecl
->getParent()->lookupInBases(BaseMatchesCallback
, aPaths
))
486 return similarMethod
;
489 CXXMemberCallExpr
const * UnnecessaryOverride::extractCallExpr(Expr
const *returnExpr
)
491 returnExpr
= returnExpr
->IgnoreImplicit();
495 // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery(
496 // const rtl::OUString& sql)
497 // throw(SQLException, RuntimeException, std::exception)
499 // return OCommonStatement::executeQuery( sql );
502 // look down through all the
505 // `-ExprWithCleanups
506 // `-CXXConstructExpr
507 // `-MaterializeTemporaryExpr
508 // `-ImplicitCastExpr
509 // `-CXXBindTemporaryExpr
510 // `-CXXMemberCallExpr
512 // where the fact that the overriding and overridden function have identical
513 // return types makes us confident that all we need to check here is whether
514 // there's an (arbitrary, one-argument) CXXConstructorExpr and
515 // CXXBindTemporaryExpr in between:
516 if (auto ctorExpr
= dyn_cast
<CXXConstructExpr
>(returnExpr
)) {
517 if (ctorExpr
->getNumArgs() == 1) {
518 auto tempExpr1
= ctorExpr
->getArg(0)->IgnoreImplicit();
519 if (auto tempExpr2
= dyn_cast
<CXXBindTemporaryExpr
>(tempExpr1
))
521 returnExpr
= tempExpr2
->getSubExpr();
523 else if (auto tempExpr2
= dyn_cast
<CXXMemberCallExpr
>(tempExpr1
))
525 returnExpr
= tempExpr2
;
530 return dyn_cast
<CXXMemberCallExpr
>(returnExpr
->IgnoreParenImpCasts());
533 loplugin::Plugin::Registration
< UnnecessaryOverride
> unnecessaryoverride("unnecessaryoverride", true);
537 #endif // LO_CLANG_SHARED_PLUGINS
539 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */