Update git submodules
[LibreOffice.git] / compilerplugins / clang / unnecessaryoverride.cxx
blobd3c67b492a19647d7be854dfe179cbdcad92179d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #ifndef LO_CLANG_SHARED_PLUGINS
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <set>
18 #include <clang/AST/CXXInheritance.h>
19 #include "config_clang.h"
20 #include "plugin.hxx"
21 #include "check.hxx"
23 /**
24 look for methods where all they do is call their superclass method
27 namespace {
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();
35 if (cls == nullptr) {
36 assert(i->getType()->isDependentType());
37 // Conservatively assume "yes" for dependent bases:
38 return true;
40 if (cls->getCanonicalDecl() == canonicBase) {
41 if (i->isVirtual()) {
42 if (hasAsNonVirtualBase) {
43 return true;
45 hasAsVirtualBase = true;
46 } else {
47 if (hasAsNonVirtualBase || hasAsVirtualBase) {
48 return true;
50 hasAsNonVirtualBase = true;
52 } else if (hasMultipleBaseInstances_(
53 cls, canonicBase, hasAsNonVirtualBase, hasAsVirtualBase))
55 return true;
58 return false;
61 bool hasMultipleBaseInstances(
62 CXXRecordDecl const * derived, CXXRecordDecl const * base)
64 bool nonVirt = false;
65 bool virt = false;
66 return hasMultipleBaseInstances_(
67 derived, base->getCanonicalDecl(), nonVirt, virt);
70 class UnnecessaryOverride:
71 public loplugin::FilteringPlugin<UnnecessaryOverride>
73 public:
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"))
81 return false;
82 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Date.cxx"))
83 return false;
84 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Time.cxx"))
85 return false;
86 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/hyperdlg.cxx"))
87 return false;
88 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/rubydialog.cxx"))
89 return false;
90 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/"))
91 return false;
92 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/view/spelldialog.cxx"))
93 return false;
94 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/dlg/SpellDialogChildWindow.cxx"))
95 return false;
96 // HAVE_ODBC_ADMINISTRATION
97 if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dsselect.cxx"))
98 return false;
99 if (loplugin::isSamePathname(fn, SRCDIR "/unotools/source/streaming/streamhelper.cxx"))
100 return false;
101 return true;
104 virtual void run() override
106 if( preRun())
107 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
110 bool VisitCXXMethodDecl(const CXXMethodDecl *);
112 private:
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())) {
121 return true;
123 if (isa<CXXConstructorDecl>(methodDecl)) {
124 return true;
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"))
135 return true;
137 // Warn about unnecessarily user-declared destructors.
138 // A destructor is deemed unnecessary if:
139 // * it is public;
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
143 // be incomplete);
144 // or
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)
166 return true;
168 if (!compiler.getSourceManager().isInMainFile(
169 methodDecl->getCanonicalDecl()->getLocation())
170 && !( methodDecl->isInlined()))
172 return true;
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>();
181 if (baseRecordType)
183 const CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(baseRecordType->getDecl());
184 if (baseRecordDecl && baseRecordDecl->getDestructor()
185 && baseRecordDecl->getDestructor()->isVirtual())
187 baseWithVirtualDtor = true;
188 break;
192 if (!baseWithVirtualDtor)
194 return true;
197 if (methodDecl->isExplicitlyDefaulted()) {
198 if (methodDecl->getPreviousDecl() != nullptr) {
199 return true;
201 } else {
202 if (!methodDecl->doesThisDeclarationHaveABody()
203 || methodDecl->isLateTemplateParsed())
205 return true;
207 auto stmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
208 if (stmt == nullptr || stmt->size() != 0) {
209 return true;
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());
229 return true;
231 report(
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();
240 return true;
243 if (!methodDecl->doesThisDeclarationHaveABody()
244 || methodDecl->isLateTemplateParsed())
246 return true;
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)
255 return true;
257 if (hasMultipleBaseInstances(
258 methodDecl->getParent(),
259 (*methodDecl->begin_overridden_methods())->getParent()))
261 return true;
265 const CXXMethodDecl* overriddenMethodDecl = findOverriddenOrSimilarMethodInSuperclasses(methodDecl);
266 if (!overriddenMethodDecl) {
267 return true;
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)
279 return true;
283 if (methodDecl->getReturnType().getCanonicalType()
284 != overriddenMethodDecl->getReturnType().getCanonicalType())
286 return true;
289 //TODO: check for identical exception specifications
291 const CompoundStmt* compoundStmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
292 if (!compoundStmt || compoundStmt->size() > 2)
293 return true;
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());
304 else
306 auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin());
307 if (returnStmt == nullptr) {
308 return true;
310 auto returnExpr = returnStmt->getRetValue();
311 if (returnExpr == nullptr) {
312 return true;
314 callExpr = extractCallExpr(returnExpr);
317 else if (!methodDecl->getReturnType().getCanonicalType()->isVoidType())
319 /** handle constructions like
320 bool foo() {
321 bool ret = Base::foo();
322 return ret;
325 auto bodyIt = compoundStmt->body_begin();
326 if (bodyIt == compoundStmt->body_end()) {
327 return true;
329 auto declStmt = dyn_cast<DeclStmt>(*bodyIt);
330 if (!declStmt || !declStmt->isSingleDecl())
331 return true;
332 auto varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
333 ++bodyIt;
334 auto returnStmt = dyn_cast<ReturnStmt>(*bodyIt);
335 if (!varDecl || !returnStmt)
336 return true;
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))
345 return true;
346 callExpr = extractCallExpr(varDecl->getInit());
349 if (!callExpr || callExpr->getMethodDecl() != overriddenMethodDecl)
350 return true;
351 const Expr* expr1 = callExpr->getImplicitObjectArgument()->IgnoreImpCasts();
352 if (!expr1)
353 return true;
354 const CXXThisExpr* expr2 = dyn_cast_or_null<CXXThisExpr>(expr1);
355 if (!expr2)
356 return true;
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))
366 return true;
369 const CXXMethodDecl* pOther = nullptr;
370 if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation())
371 pOther = methodDecl->getCanonicalDecl();
373 if (pOther) {
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"))
378 return true;
381 if (containsPreprocessingConditionalInclusion(methodDecl->getBody()->getSourceRange())) {
382 return true;
385 report(
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();
394 report(
395 DiagnosticsEngine::Note,
396 "method declaration here",
397 pOther->getLocation())
398 << pOther->getSourceRange();
400 return true;
403 const CXXMethodDecl* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl* methodDecl)
405 if (methodDecl->isVirtual()) {
406 return *methodDecl->begin_overridden_methods();
408 if (!methodDecl->getDeclName().isIdentifier()) {
409 return nullptr;
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)
418 return false;
419 if (!cxxBaseSpecifier->getType().getTypePtr())
420 return false;
421 const CXXRecordDecl* baseCXXRecordDecl = cxxBaseSpecifier->getType()->getAsCXXRecordDecl();
422 if (!baseCXXRecordDecl)
423 return false;
424 if (baseCXXRecordDecl->isInvalidDecl())
425 return false;
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())
432 continue;
433 if (!baseMethod->getDeclName().isIdentifier() || methodDecl->getName() != baseMethod->getName()) {
434 continue;
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())
443 continue;
445 if (methodDecl->getReturnType().getCanonicalType()
446 != baseMethod->getReturnType().getCanonicalType())
448 continue;
450 if (methodDecl->getNumParams() != baseMethod->getNumParams())
451 continue;
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;
458 break;
461 if (bParamsMatch)
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;
468 if (similarMethod
469 && similarBasePath.size() < path.size()
470 && std::equal(similarBasePath.begin(), similarBasePath.end(),
471 path.begin(), Compare))
472 break;
473 if (similarMethod)
474 return true; // short circuit the process
475 similarMethod = baseMethod;
476 similarBasePath = path;
479 return false;
482 CXXBasePaths aPaths;
483 if (methodDecl->getParent()->lookupInBases(BaseMatchesCallback, aPaths))
484 return nullptr;
486 return similarMethod;
489 CXXMemberCallExpr const * UnnecessaryOverride::extractCallExpr(Expr const *returnExpr)
491 returnExpr = returnExpr->IgnoreImplicit();
493 // In something like
495 // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery(
496 // const rtl::OUString& sql)
497 // throw(SQLException, RuntimeException, std::exception)
498 // {
499 // return OCommonStatement::executeQuery( sql );
500 // }
502 // look down through all the
504 // ReturnStmt
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: */