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/.
16 #include <clang/AST/CXXInheritance.h>
20 look for methods where all they do is call their superclass method
25 bool hasMultipleBaseInstances_(
26 CXXRecordDecl
const * derived
, CXXRecordDecl
const * canonicBase
,
27 bool & hasAsNonVirtualBase
, bool & hasAsVirtualBase
)
29 for (auto i
= derived
->bases_begin(); i
!= derived
->bases_end(); ++i
) {
30 auto const cls
= i
->getType()->getAsCXXRecordDecl();
32 assert(i
->getType()->isDependentType());
33 // Conservatively assume "yes" for dependent bases:
36 if (cls
->getCanonicalDecl() == canonicBase
) {
38 if (hasAsNonVirtualBase
) {
41 hasAsVirtualBase
= true;
43 if (hasAsNonVirtualBase
|| hasAsVirtualBase
) {
46 hasAsNonVirtualBase
= true;
48 } else if (hasMultipleBaseInstances_(
49 cls
, canonicBase
, hasAsNonVirtualBase
, hasAsVirtualBase
))
57 bool hasMultipleBaseInstances(
58 CXXRecordDecl
const * derived
, CXXRecordDecl
const * base
)
62 return hasMultipleBaseInstances_(
63 derived
, base
->getCanonicalDecl(), nonVirt
, virt
);
66 class UnnecessaryOverride
:
67 public RecursiveASTVisitor
<UnnecessaryOverride
>, public loplugin::Plugin
70 explicit UnnecessaryOverride(loplugin::InstantiationData
const & data
): Plugin(data
) {}
72 virtual void run() override
74 // ignore some files with problematic macros
75 StringRef
fn( compiler
.getSourceManager().getFileEntryForID(
76 compiler
.getSourceManager().getMainFileID())->getName() );
77 if (loplugin::isSamePathname(fn
, SRCDIR
"/sd/source/ui/framework/factories/ChildWindowPane.cxx"))
79 if (loplugin::isSamePathname(fn
, SRCDIR
"/forms/source/component/Date.cxx"))
81 if (loplugin::isSamePathname(fn
, SRCDIR
"/forms/source/component/Time.cxx"))
83 if (loplugin::isSamePathname(fn
, SRCDIR
"/svx/source/dialog/hyperdlg.cxx"))
85 if (loplugin::isSamePathname(fn
, SRCDIR
"/svx/source/dialog/rubydialog.cxx"))
87 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/canvas/"))
89 if (loplugin::isSamePathname(fn
, SRCDIR
"/sc/source/ui/view/spelldialog.cxx"))
91 if (loplugin::isSamePathname(fn
, SRCDIR
"/sd/source/ui/dlg/SpellDialogChildWindow.cxx"))
93 // HAVE_ODBC_ADMINISTRATION
94 if (loplugin::isSamePathname(fn
, SRCDIR
"/dbaccess/source/ui/dlg/dsselect.cxx"))
97 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
100 bool VisitCXXMethodDecl(const CXXMethodDecl
*);
103 const CXXMethodDecl
* findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl
*);
104 bool BaseCheckCallback(const CXXRecordDecl
*BaseDefinition
);
107 bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl
* methodDecl
)
109 if (ignoreLocation(methodDecl
->getCanonicalDecl())) {
112 if (isa
<CXXConstructorDecl
>(methodDecl
)) {
116 StringRef aFileName
= compiler
.getSourceManager().getFilename(compiler
.getSourceManager().getSpellingLoc(methodDecl
->getLocStart()));
118 if (isa
<CXXDestructorDecl
>(methodDecl
)
119 && !isInUnoIncludeFile(methodDecl
))
121 // the code is this method is only __compiled__ if OSL_DEBUG_LEVEL > 1
122 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/tools/source/stream/strmunx.cxx"))
125 // Warn about unnecessarily user-declared destructors.
126 // A destructor is deemed unnecessary if:
128 // * its class is only defined in the .cxx file (i.e., the virtual
129 // destructor is neither used to control the place of vtable
130 // emission, nor is its definition depending on types that may still
133 // the destructor is inline, the class definition is complete,
134 // and the class has no superclasses
135 // * it either does not have an explicit exception specification, or has
136 // a non-dependent explicit exception specification that is compatible
137 // with a non-dependent exception specification the destructor would
138 // have if it did not have an explicit one (TODO);
139 // * it is either defined as defaulted or with an empty body.
140 // Removing the user-declared destructor may cause the class to get an
141 // implicitly declared move constructor and/or move assignment operator;
142 // that is considered acceptable: If any subobject cannot be moved, the
143 // implicitly declared function will be defined as deleted (which is in
144 // practice not much different from not having it declared), and
145 // otherwise offering movability is likely even an improvement over not
146 // offering it due to a "pointless" user-declared destructor.
147 // Similarly, removing the user-declared destructor may cause the
148 // implicit definition of a copy constructor and/or copy assignment
149 // operator to change from being an obsolete feature to being a standard
150 // feature. That difference is not taken into account here.
151 auto cls
= methodDecl
->getParent();
152 if (methodDecl
->getAccess() != AS_public
)
156 if (!compiler
.getSourceManager().isInMainFile(
157 methodDecl
->getCanonicalDecl()->getLocation())
158 && !( methodDecl
->isInlined()))
162 // if it's virtual, but it has a base-class with a non-virtual destructor
163 if (methodDecl
->isVirtual())
165 bool baseWithVirtualDtor
= false;
166 for (auto baseSpecifier
= cls
->bases_begin(); baseSpecifier
!= cls
->bases_end(); ++baseSpecifier
)
168 const RecordType
* baseRecordType
= baseSpecifier
->getType()->getAs
<RecordType
>();
171 const CXXRecordDecl
* baseRecordDecl
= dyn_cast
<CXXRecordDecl
>(baseRecordType
->getDecl());
172 if (baseRecordDecl
&& baseRecordDecl
->getDestructor()
173 && baseRecordDecl
->getDestructor()->isVirtual())
175 baseWithVirtualDtor
= true;
180 if (!baseWithVirtualDtor
)
186 if (methodDecl
->isInlined()
187 && compiler
.getSourceManager().isInMainFile(methodDecl
->getLocation())
188 && !compiler
.getSourceManager().isInMainFile(methodDecl
->getCanonicalDecl()->getLocation()))
192 if (!methodDecl
->isExplicitlyDefaulted()) {
193 if (!methodDecl
->doesThisDeclarationHaveABody()
194 || methodDecl
->isLateTemplateParsed())
198 auto stmt
= dyn_cast
<CompoundStmt
>(methodDecl
->getBody());
199 if (stmt
== nullptr || stmt
->size() != 0) {
203 //TODO: exception specification
204 if (!(cls
->hasUserDeclaredCopyConstructor()
205 || cls
->hasUserDeclaredCopyAssignment()
206 || cls
->hasUserDeclaredMoveConstructor()
207 || cls
->hasUserDeclaredMoveAssignment()))
210 if ((cls
->needsImplicitMoveConstructor()
211 && !(cls
->hasUserDeclaredCopyConstructor()
212 || cls
->hasUserDeclaredCopyAssignment()
213 || cls
->hasUserDeclaredMoveAssignment()))
214 || (cls
->needsImplicitMoveAssignment()
215 && !(cls
->hasUserDeclaredCopyConstructor()
216 || cls
->hasUserDeclaredCopyAssignment()
217 || cls
->hasUserDeclaredMoveConstructor())))
219 report(DiagnosticsEngine::Fatal
, "TODO", methodDecl
->getLocation());
223 DiagnosticsEngine::Warning
, "unnecessary user-declared destructor",
224 methodDecl
->getLocation())
225 << methodDecl
->getSourceRange();
226 auto cd
= methodDecl
->getCanonicalDecl();
227 if (cd
->getLocation() != methodDecl
->getLocation()) {
228 report(DiagnosticsEngine::Note
, "declared here", cd
->getLocation())
229 << cd
->getSourceRange();
234 if (!methodDecl
->doesThisDeclarationHaveABody()
235 || methodDecl
->isLateTemplateParsed())
240 // If overriding more than one base member function, or one base member
241 // function that is available in multiple (non-virtual) base class
242 // instances, then this is a disambiguating override:
243 if (methodDecl
->isVirtual()) {
244 if (methodDecl
->size_overridden_methods() != 1)
248 if (hasMultipleBaseInstances(
249 methodDecl
->getParent(),
250 (*methodDecl
->begin_overridden_methods())->getParent()))
256 const CXXMethodDecl
* overriddenMethodDecl
= findOverriddenOrSimilarMethodInSuperclasses(methodDecl
);
257 if (!overriddenMethodDecl
) {
261 // Check for differences in default parameters:
262 unsigned const numParams
= methodDecl
->getNumParams();
263 assert(overriddenMethodDecl
->getNumParams() == numParams
);
264 for (unsigned i
= 0; i
!= numParams
; ++i
) {
265 if (checkIdenticalDefaultArguments(
266 methodDecl
->getParamDecl(i
)->getDefaultArg(),
267 overriddenMethodDecl
->getParamDecl(i
)->getDefaultArg())
268 != IdenticalDefaultArgumentsResult::Yes
)
274 if (methodDecl
->getReturnType().getCanonicalType()
275 != overriddenMethodDecl
->getReturnType().getCanonicalType())
280 //TODO: check for identical exception specifications
282 const CompoundStmt
* compoundStmt
= dyn_cast
<CompoundStmt
>(methodDecl
->getBody());
283 if (!compoundStmt
|| compoundStmt
->size() != 1)
286 const CXXMemberCallExpr
* callExpr
= nullptr;
287 if (methodDecl
->getReturnType().getCanonicalType()->isVoidType())
289 if (auto const e
= dyn_cast
<Expr
>(*compoundStmt
->body_begin())) {
290 callExpr
= dyn_cast
<CXXMemberCallExpr
>(e
->IgnoreImplicit()->IgnoreParens());
295 auto returnStmt
= dyn_cast
<ReturnStmt
>(*compoundStmt
->body_begin());
296 if (returnStmt
== nullptr) {
299 auto returnExpr
= returnStmt
->getRetValue();
300 if (returnExpr
== nullptr) {
303 returnExpr
= returnExpr
->IgnoreImplicit();
307 // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery(
308 // const rtl::OUString& sql)
309 // throw(SQLException, RuntimeException, std::exception)
311 // return OCommonStatement::executeQuery( sql );
314 // look down through all the
317 // `-ExprWithCleanups
318 // `-CXXConstructExpr
319 // `-MaterializeTemporaryExpr
320 // `-ImplicitCastExpr
321 // `-CXXBindTemporaryExpr
322 // `-CXXMemberCallExpr
324 // where the fact that the overriding and overridden function have identical
325 // return types makes us confident that all we need to check here is whether
326 // there's an (arbitrary, one-argument) CXXConstructorExpr and
327 // CXXBindTemporaryExpr in between:
328 if (auto ctorExpr
= dyn_cast
<CXXConstructExpr
>(returnExpr
)) {
329 if (ctorExpr
->getNumArgs() == 1) {
330 auto tempExpr1
= ctorExpr
->getArg(0)->IgnoreImplicit();
331 if (auto tempExpr2
= dyn_cast
<CXXBindTemporaryExpr
>(tempExpr1
))
333 returnExpr
= tempExpr2
->getSubExpr();
335 else if (auto tempExpr2
= dyn_cast
<CXXMemberCallExpr
>(tempExpr1
))
337 returnExpr
= tempExpr2
;
342 callExpr
= dyn_cast
<CXXMemberCallExpr
>(returnExpr
->IgnoreParenImpCasts());
345 if (!callExpr
|| callExpr
->getMethodDecl() != overriddenMethodDecl
)
347 const Expr
* expr1
= callExpr
->getImplicitObjectArgument()->IgnoreImpCasts();
350 const CXXThisExpr
* expr2
= dyn_cast_or_null
<CXXThisExpr
>(expr1
);
353 for (unsigned i
= 0; i
<callExpr
->getNumArgs(); ++i
) {
354 auto e
= callExpr
->getArg(i
)->IgnoreImplicit();
355 if (auto const e1
= dyn_cast
<CXXConstructExpr
>(e
)) {
356 if (e1
->getConstructor()->isCopyOrMoveConstructor() && e1
->getNumArgs() == 1) {
357 e
= e1
->getArg(0)->IgnoreImpCasts();
360 const DeclRefExpr
* declRefExpr
= dyn_cast
<DeclRefExpr
>(e
);
361 if (!declRefExpr
|| declRefExpr
->getDecl() != methodDecl
->getParamDecl(i
))
366 DiagnosticsEngine::Warning
, "%0%1 function just calls %2 parent",
367 methodDecl
->getLocation())
368 << methodDecl
->getAccess()
369 << (methodDecl
->isVirtual() ? " virtual" : "")
370 << overriddenMethodDecl
->getAccess()
371 << methodDecl
->getSourceRange();
372 if (methodDecl
->getCanonicalDecl()->getLocation() != methodDecl
->getLocation()) {
373 const CXXMethodDecl
* pOther
= methodDecl
->getCanonicalDecl();
375 DiagnosticsEngine::Note
,
376 "method declaration here",
377 pOther
->getLocation())
378 << pOther
->getSourceRange();
383 const CXXMethodDecl
* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl
* methodDecl
)
385 if (methodDecl
->isVirtual()) {
386 return *methodDecl
->begin_overridden_methods();
388 if (!methodDecl
->getDeclName().isIdentifier()) {
392 std::vector
<const CXXMethodDecl
*> maSimilarMethods
;
394 auto BaseMatchesCallback
= [&](const CXXBaseSpecifier
*cxxBaseSpecifier
, CXXBasePath
& path
)
396 if (cxxBaseSpecifier
->getAccessSpecifier() != AS_public
&& cxxBaseSpecifier
->getAccessSpecifier() != AS_protected
)
398 if (!cxxBaseSpecifier
->getType().getTypePtr())
400 const CXXRecordDecl
* baseCXXRecordDecl
= cxxBaseSpecifier
->getType()->getAsCXXRecordDecl();
401 if (!baseCXXRecordDecl
)
403 if (baseCXXRecordDecl
->isInvalidDecl())
405 for (const CXXMethodDecl
* baseMethod
: baseCXXRecordDecl
->methods())
407 auto effectiveBaseMethodAccess
= baseMethod
->getAccess();
408 if (effectiveBaseMethodAccess
== AS_public
&& path
.Access
== AS_protected
)
409 effectiveBaseMethodAccess
= AS_protected
;
410 if (effectiveBaseMethodAccess
!= methodDecl
->getAccess())
412 if (!baseMethod
->getDeclName().isIdentifier() || methodDecl
->getName() != baseMethod
->getName()) {
415 if (methodDecl
->isStatic() != baseMethod
->isStatic()
416 || methodDecl
->isConst() != baseMethod
->isConst()
417 || methodDecl
->isVolatile() != baseMethod
->isVolatile()
418 || (methodDecl
->getRefQualifier()
419 != baseMethod
->getRefQualifier())
420 || methodDecl
->isVariadic() != baseMethod
->isVariadic())
424 if (methodDecl
->getReturnType().getCanonicalType()
425 != baseMethod
->getReturnType().getCanonicalType())
429 if (methodDecl
->getNumParams() != baseMethod
->getNumParams())
431 bool bParamsMatch
= true;
432 for (unsigned i
=0; i
<methodDecl
->param_size(); ++i
)
434 if (methodDecl
->parameters()[i
]->getType() != baseMethod
->parameters()[i
]->getType())
436 bParamsMatch
= false;
441 maSimilarMethods
.push_back(baseMethod
);
447 methodDecl
->getParent()->lookupInBases(BaseMatchesCallback
, aPaths
);
449 if (maSimilarMethods
.size() == 1) {
450 return maSimilarMethods
[0];
456 loplugin::Plugin::Registration
< UnnecessaryOverride
> X("unnecessaryoverride", true);
460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */