bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / unnecessaryoverride.cxx
blob4d4cee3227514ab573191b6630bf60aec946823c
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 "plugin.hxx"
21 /**
22 look for methods where all they do is call their superclass method
25 namespace {
27 bool hasMultipleBaseInstances_(
28 CXXRecordDecl const * derived, CXXRecordDecl const * canonicBase,
29 bool & hasAsNonVirtualBase, bool & hasAsVirtualBase)
31 for (auto i = derived->bases_begin(); i != derived->bases_end(); ++i) {
32 auto const cls = i->getType()->getAsCXXRecordDecl();
33 if (cls == nullptr) {
34 assert(i->getType()->isDependentType());
35 // Conservatively assume "yes" for dependent bases:
36 return true;
38 if (cls->getCanonicalDecl() == canonicBase) {
39 if (i->isVirtual()) {
40 if (hasAsNonVirtualBase) {
41 return true;
43 hasAsVirtualBase = true;
44 } else {
45 if (hasAsNonVirtualBase || hasAsVirtualBase) {
46 return true;
48 hasAsNonVirtualBase = true;
50 } else if (hasMultipleBaseInstances_(
51 cls, canonicBase, hasAsNonVirtualBase, hasAsVirtualBase))
53 return true;
56 return false;
59 bool hasMultipleBaseInstances(
60 CXXRecordDecl const * derived, CXXRecordDecl const * base)
62 bool nonVirt = false;
63 bool virt = false;
64 return hasMultipleBaseInstances_(
65 derived, base->getCanonicalDecl(), nonVirt, virt);
68 class UnnecessaryOverride:
69 public loplugin::FilteringPlugin<UnnecessaryOverride>
71 public:
72 explicit UnnecessaryOverride(loplugin::InstantiationData const & data): FilteringPlugin(data) {}
74 virtual bool preRun() override
76 // ignore some files with problematic macros
77 StringRef fn(handler.getMainFileName());
78 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/framework/factories/ChildWindowPane.cxx"))
79 return false;
80 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Date.cxx"))
81 return false;
82 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Time.cxx"))
83 return false;
84 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/hyperdlg.cxx"))
85 return false;
86 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/rubydialog.cxx"))
87 return false;
88 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/"))
89 return false;
90 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/view/spelldialog.cxx"))
91 return false;
92 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/dlg/SpellDialogChildWindow.cxx"))
93 return false;
94 // HAVE_ODBC_ADMINISTRATION
95 if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dsselect.cxx"))
96 return false;
97 return true;
100 virtual void run() override
102 if( preRun())
103 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
106 bool VisitCXXMethodDecl(const CXXMethodDecl *);
108 private:
109 const CXXMethodDecl * findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl *);
110 bool BaseCheckCallback(const CXXRecordDecl *BaseDefinition);
111 CXXMemberCallExpr const * extractCallExpr(Expr const *);
114 bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
116 if (ignoreLocation(methodDecl->getCanonicalDecl())) {
117 return true;
119 if (isa<CXXConstructorDecl>(methodDecl)) {
120 return true;
123 StringRef aFileName = getFileNameOfSpellingLoc(
124 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(methodDecl)));
126 if (isa<CXXDestructorDecl>(methodDecl)
127 && !isInUnoIncludeFile(methodDecl))
129 // the code is this method is only __compiled__ if OSL_DEBUG_LEVEL > 1
130 if (loplugin::isSamePathname(aFileName, SRCDIR "/tools/source/stream/strmunx.cxx"))
131 return true;
133 // Warn about unnecessarily user-declared destructors.
134 // A destructor is deemed unnecessary if:
135 // * it is public;
136 // * its class is only defined in the .cxx file (i.e., the virtual
137 // destructor is neither used to control the place of vtable
138 // emission, nor is its definition depending on types that may still
139 // be incomplete);
140 // or
141 // the destructor is inline, the class definition is complete,
142 // and the class has no superclasses
143 // * it either does not have an explicit exception specification, or has
144 // a non-dependent explicit exception specification that is compatible
145 // with a non-dependent exception specification the destructor would
146 // have if it did not have an explicit one (TODO);
147 // * it is either defined as defaulted or with an empty body.
148 // Removing the user-declared destructor may cause the class to get an
149 // implicitly declared move constructor and/or move assignment operator;
150 // that is considered acceptable: If any subobject cannot be moved, the
151 // implicitly declared function will be defined as deleted (which is in
152 // practice not much different from not having it declared), and
153 // otherwise offering movability is likely even an improvement over not
154 // offering it due to a "pointless" user-declared destructor.
155 // Similarly, removing the user-declared destructor may cause the
156 // implicit definition of a copy constructor and/or copy assignment
157 // operator to change from being an obsolete feature to being a standard
158 // feature. That difference is not taken into account here.
159 auto cls = methodDecl->getParent();
160 if (methodDecl->getAccess() != AS_public)
162 return true;
164 if (!compiler.getSourceManager().isInMainFile(
165 methodDecl->getCanonicalDecl()->getLocation())
166 && !( methodDecl->isInlined()))
168 return true;
170 // if it's virtual, but it has a base-class with a non-virtual destructor
171 if (methodDecl->isVirtual())
173 bool baseWithVirtualDtor = false;
174 for (auto baseSpecifier = cls->bases_begin(); baseSpecifier != cls->bases_end(); ++baseSpecifier)
176 const RecordType* baseRecordType = baseSpecifier->getType()->getAs<RecordType>();
177 if (baseRecordType)
179 const CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(baseRecordType->getDecl());
180 if (baseRecordDecl && baseRecordDecl->getDestructor()
181 && baseRecordDecl->getDestructor()->isVirtual())
183 baseWithVirtualDtor = true;
184 break;
188 if (!baseWithVirtualDtor)
190 return true;
193 // corner case
194 if (methodDecl->isInlined()
195 && compiler.getSourceManager().isInMainFile(methodDecl->getLocation())
196 && !compiler.getSourceManager().isInMainFile(methodDecl->getCanonicalDecl()->getLocation()))
198 return true;
200 if (!methodDecl->isExplicitlyDefaulted()) {
201 if (!methodDecl->doesThisDeclarationHaveABody()
202 || methodDecl->isLateTemplateParsed())
204 return true;
206 auto stmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
207 if (stmt == nullptr || stmt->size() != 0) {
208 return true;
211 //TODO: exception specification
212 if (!(cls->hasUserDeclaredCopyConstructor()
213 || cls->hasUserDeclaredCopyAssignment()
214 || cls->hasUserDeclaredMoveConstructor()
215 || cls->hasUserDeclaredMoveAssignment()))
218 if ((cls->needsImplicitMoveConstructor()
219 && !(cls->hasUserDeclaredCopyConstructor()
220 || cls->hasUserDeclaredCopyAssignment()
221 || cls->hasUserDeclaredMoveAssignment()))
222 || (cls->needsImplicitMoveAssignment()
223 && !(cls->hasUserDeclaredCopyConstructor()
224 || cls->hasUserDeclaredCopyAssignment()
225 || cls->hasUserDeclaredMoveConstructor())))
227 report(DiagnosticsEngine::Fatal, "TODO", methodDecl->getLocation());
228 return true;
230 report(
231 DiagnosticsEngine::Warning, "unnecessary user-declared destructor",
232 methodDecl->getLocation())
233 << methodDecl->getSourceRange();
234 auto cd = methodDecl->getCanonicalDecl();
235 if (cd->getLocation() != methodDecl->getLocation()) {
236 report(DiagnosticsEngine::Note, "declared here", cd->getLocation())
237 << cd->getSourceRange();
239 return true;
242 if (!methodDecl->doesThisDeclarationHaveABody()
243 || methodDecl->isLateTemplateParsed())
245 return true;
248 // If overriding more than one base member function, or one base member
249 // function that is available in multiple (non-virtual) base class
250 // instances, then this is a disambiguating override:
251 if (methodDecl->isVirtual()) {
252 if (methodDecl->size_overridden_methods() != 1)
254 return true;
256 if (hasMultipleBaseInstances(
257 methodDecl->getParent(),
258 (*methodDecl->begin_overridden_methods())->getParent()))
260 return true;
264 const CXXMethodDecl* overriddenMethodDecl = findOverriddenOrSimilarMethodInSuperclasses(methodDecl);
265 if (!overriddenMethodDecl) {
266 return true;
269 // Check for differences in default parameters:
270 unsigned const numParams = methodDecl->getNumParams();
271 assert(overriddenMethodDecl->getNumParams() == numParams);
272 for (unsigned i = 0; i != numParams; ++i) {
273 if (checkIdenticalDefaultArguments(
274 methodDecl->getParamDecl(i)->getDefaultArg(),
275 overriddenMethodDecl->getParamDecl(i)->getDefaultArg())
276 != IdenticalDefaultArgumentsResult::Yes)
278 return true;
282 if (methodDecl->getReturnType().getCanonicalType()
283 != overriddenMethodDecl->getReturnType().getCanonicalType())
285 return true;
288 //TODO: check for identical exception specifications
290 const CompoundStmt* compoundStmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
291 if (!compoundStmt || compoundStmt->size() > 2)
292 return true;
294 const CXXMemberCallExpr* callExpr = nullptr;
295 if (compoundStmt->size() == 1)
297 if (methodDecl->getReturnType().getCanonicalType()->isVoidType())
299 if (auto const e = dyn_cast<Expr>(*compoundStmt->body_begin())) {
300 callExpr = dyn_cast<CXXMemberCallExpr>(e->IgnoreImplicit()->IgnoreParens());
303 else
305 auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin());
306 if (returnStmt == nullptr) {
307 return true;
309 auto returnExpr = returnStmt->getRetValue();
310 if (returnExpr == nullptr) {
311 return true;
313 callExpr = extractCallExpr(returnExpr);
316 else if (!methodDecl->getReturnType().getCanonicalType()->isVoidType())
318 /** handle constructions like
319 bool foo() {
320 bool ret = Base::foo();
321 return ret;
324 auto bodyIt = compoundStmt->body_begin();
325 auto declStmt = dyn_cast<DeclStmt>(*bodyIt);
326 if (!declStmt || !declStmt->isSingleDecl())
327 return true;
328 auto varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
329 ++bodyIt;
330 auto returnStmt = dyn_cast<ReturnStmt>(*bodyIt);
331 if (!varDecl || !returnStmt)
332 return true;
333 Expr const * retValue = returnStmt->getRetValue()->IgnoreParenImpCasts();
334 if (auto exprWithCleanups = dyn_cast<ExprWithCleanups>(retValue))
335 retValue = exprWithCleanups->getSubExpr()->IgnoreParenImpCasts();
336 if (auto constructExpr = dyn_cast<CXXConstructExpr>(retValue)) {
337 if (constructExpr->getNumArgs() == 1)
338 retValue = constructExpr->getArg(0)->IgnoreParenImpCasts();
340 if (!isa<DeclRefExpr>(retValue))
341 return true;
342 callExpr = extractCallExpr(varDecl->getInit());
345 if (!callExpr || callExpr->getMethodDecl() != overriddenMethodDecl)
346 return true;
347 const Expr* expr1 = callExpr->getImplicitObjectArgument()->IgnoreImpCasts();
348 if (!expr1)
349 return true;
350 const CXXThisExpr* expr2 = dyn_cast_or_null<CXXThisExpr>(expr1);
351 if (!expr2)
352 return true;
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))
362 return true;
365 const CXXMethodDecl* pOther = nullptr;
366 if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation())
367 pOther = methodDecl->getCanonicalDecl();
369 if (pOther) {
370 StringRef aFileName = getFileNameOfSpellingLoc(
371 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(pOther)));
372 // SFX_DECL_CHILDWINDOW_WITHID macro
373 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/sfx2/childwin.hxx"))
374 return true;
377 report(
378 DiagnosticsEngine::Warning, "%0%1 function just calls %2 parent",
379 methodDecl->getLocation())
380 << methodDecl->getAccess()
381 << (methodDecl->isVirtual() ? " virtual" : "")
382 << overriddenMethodDecl->getAccess()
383 << methodDecl->getSourceRange();
384 if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation()) {
385 const CXXMethodDecl* pOther = methodDecl->getCanonicalDecl();
386 report(
387 DiagnosticsEngine::Note,
388 "method declaration here",
389 pOther->getLocation())
390 << pOther->getSourceRange();
392 return true;
395 const CXXMethodDecl* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl* methodDecl)
397 if (methodDecl->isVirtual()) {
398 return *methodDecl->begin_overridden_methods();
400 if (!methodDecl->getDeclName().isIdentifier()) {
401 return nullptr;
404 const CXXMethodDecl* similarMethod = nullptr;
405 CXXBasePath similarBasePath;
407 auto BaseMatchesCallback = [&](const CXXBaseSpecifier *cxxBaseSpecifier, CXXBasePath& path)
409 if (cxxBaseSpecifier->getAccessSpecifier() != AS_public && cxxBaseSpecifier->getAccessSpecifier() != AS_protected)
410 return false;
411 if (!cxxBaseSpecifier->getType().getTypePtr())
412 return false;
413 const CXXRecordDecl* baseCXXRecordDecl = cxxBaseSpecifier->getType()->getAsCXXRecordDecl();
414 if (!baseCXXRecordDecl)
415 return false;
416 if (baseCXXRecordDecl->isInvalidDecl())
417 return false;
418 for (const CXXMethodDecl* baseMethod : baseCXXRecordDecl->methods())
420 auto effectiveBaseMethodAccess = baseMethod->getAccess();
421 if (effectiveBaseMethodAccess == AS_public && path.Access == AS_protected)
422 effectiveBaseMethodAccess = AS_protected;
423 if (effectiveBaseMethodAccess != methodDecl->getAccess())
424 continue;
425 if (!baseMethod->getDeclName().isIdentifier() || methodDecl->getName() != baseMethod->getName()) {
426 continue;
428 if (methodDecl->isStatic() != baseMethod->isStatic()
429 || methodDecl->isConst() != baseMethod->isConst()
430 || methodDecl->isVolatile() != baseMethod->isVolatile()
431 || (methodDecl->getRefQualifier()
432 != baseMethod->getRefQualifier())
433 || methodDecl->isVariadic() != baseMethod->isVariadic())
435 continue;
437 if (methodDecl->getReturnType().getCanonicalType()
438 != baseMethod->getReturnType().getCanonicalType())
440 continue;
442 if (methodDecl->getNumParams() != baseMethod->getNumParams())
443 continue;
444 bool bParamsMatch = true;
445 for (unsigned i=0; i<methodDecl->param_size(); ++i)
447 if (methodDecl->parameters()[i]->getType() != baseMethod->parameters()[i]->getType())
449 bParamsMatch = false;
450 break;
453 if (bParamsMatch)
455 // if we have already found a method directly below us in the inheritance hierarchy, just ignore this one
456 auto Compare = [&](CXXBasePathElement const & lhs, CXXBasePathElement const & rhs)
458 return lhs.Class == rhs.Class;
460 if (similarMethod
461 && similarBasePath.size() < path.size()
462 && std::equal(similarBasePath.begin(), similarBasePath.end(),
463 path.begin(), Compare))
464 break;
465 if (similarMethod)
466 return true; // short circuit the process
467 similarMethod = baseMethod;
468 similarBasePath = path;
471 return false;
474 CXXBasePaths aPaths;
475 if (methodDecl->getParent()->lookupInBases(BaseMatchesCallback, aPaths))
476 return nullptr;
478 return similarMethod;
481 CXXMemberCallExpr const * UnnecessaryOverride::extractCallExpr(Expr const *returnExpr)
483 returnExpr = returnExpr->IgnoreImplicit();
485 // In something like
487 // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery(
488 // const rtl::OUString& sql)
489 // throw(SQLException, RuntimeException, std::exception)
490 // {
491 // return OCommonStatement::executeQuery( sql );
492 // }
494 // look down through all the
496 // ReturnStmt
497 // `-ExprWithCleanups
498 // `-CXXConstructExpr
499 // `-MaterializeTemporaryExpr
500 // `-ImplicitCastExpr
501 // `-CXXBindTemporaryExpr
502 // `-CXXMemberCallExpr
504 // where the fact that the overriding and overridden function have identical
505 // return types makes us confident that all we need to check here is whether
506 // there's an (arbitrary, one-argument) CXXConstructorExpr and
507 // CXXBindTemporaryExpr in between:
508 if (auto ctorExpr = dyn_cast<CXXConstructExpr>(returnExpr)) {
509 if (ctorExpr->getNumArgs() == 1) {
510 auto tempExpr1 = ctorExpr->getArg(0)->IgnoreImplicit();
511 if (auto tempExpr2 = dyn_cast<CXXBindTemporaryExpr>(tempExpr1))
513 returnExpr = tempExpr2->getSubExpr();
515 else if (auto tempExpr2 = dyn_cast<CXXMemberCallExpr>(tempExpr1))
517 returnExpr = tempExpr2;
522 return dyn_cast<CXXMemberCallExpr>(returnExpr->IgnoreParenImpCasts());
525 loplugin::Plugin::Registration< UnnecessaryOverride > unnecessaryoverride("unnecessaryoverride", true);
529 #endif // LO_CLANG_SHARED_PLUGINS
531 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */