Version 6.1.0.2, tag libreoffice-6.1.0.2
[LibreOffice.git] / compilerplugins / clang / unnecessaryoverride.cxx
blob338598985289d90958d3b77d5445541686e00aea
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 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <fstream>
14 #include <set>
16 #include <clang/AST/CXXInheritance.h>
17 #include "plugin.hxx"
19 /**
20 look for methods where all they do is call their superclass method
23 namespace {
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();
31 if (cls == nullptr) {
32 assert(i->getType()->isDependentType());
33 // Conservatively assume "yes" for dependent bases:
34 return true;
36 if (cls->getCanonicalDecl() == canonicBase) {
37 if (i->isVirtual()) {
38 if (hasAsNonVirtualBase) {
39 return true;
41 hasAsVirtualBase = true;
42 } else {
43 if (hasAsNonVirtualBase || hasAsVirtualBase) {
44 return true;
46 hasAsNonVirtualBase = true;
48 } else if (hasMultipleBaseInstances_(
49 cls, canonicBase, hasAsNonVirtualBase, hasAsVirtualBase))
51 return true;
54 return false;
57 bool hasMultipleBaseInstances(
58 CXXRecordDecl const * derived, CXXRecordDecl const * base)
60 bool nonVirt = false;
61 bool virt = false;
62 return hasMultipleBaseInstances_(
63 derived, base->getCanonicalDecl(), nonVirt, virt);
66 class UnnecessaryOverride:
67 public RecursiveASTVisitor<UnnecessaryOverride>, public loplugin::Plugin
69 public:
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"))
78 return;
79 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Date.cxx"))
80 return;
81 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Time.cxx"))
82 return;
83 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/hyperdlg.cxx"))
84 return;
85 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/rubydialog.cxx"))
86 return;
87 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/"))
88 return;
89 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/view/spelldialog.cxx"))
90 return;
91 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/dlg/SpellDialogChildWindow.cxx"))
92 return;
93 // HAVE_ODBC_ADMINISTRATION
94 if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dsselect.cxx"))
95 return;
97 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
100 bool VisitCXXMethodDecl(const CXXMethodDecl *);
102 private:
103 const CXXMethodDecl * findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl *);
104 bool BaseCheckCallback(const CXXRecordDecl *BaseDefinition);
107 bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
109 if (ignoreLocation(methodDecl->getCanonicalDecl())) {
110 return true;
112 if (isa<CXXConstructorDecl>(methodDecl)) {
113 return true;
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"))
123 return true;
125 // Warn about unnecessarily user-declared destructors.
126 // A destructor is deemed unnecessary if:
127 // * it is public;
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
131 // be incomplete);
132 // or
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)
154 return true;
156 if (!compiler.getSourceManager().isInMainFile(
157 methodDecl->getCanonicalDecl()->getLocation())
158 && !( methodDecl->isInlined()))
160 return true;
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>();
169 if (baseRecordType)
171 const CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(baseRecordType->getDecl());
172 if (baseRecordDecl && baseRecordDecl->getDestructor()
173 && baseRecordDecl->getDestructor()->isVirtual())
175 baseWithVirtualDtor = true;
176 break;
180 if (!baseWithVirtualDtor)
182 return true;
185 // corner case
186 if (methodDecl->isInlined()
187 && compiler.getSourceManager().isInMainFile(methodDecl->getLocation())
188 && !compiler.getSourceManager().isInMainFile(methodDecl->getCanonicalDecl()->getLocation()))
190 return true;
192 if (!methodDecl->isExplicitlyDefaulted()) {
193 if (!methodDecl->doesThisDeclarationHaveABody()
194 || methodDecl->isLateTemplateParsed())
196 return true;
198 auto stmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
199 if (stmt == nullptr || stmt->size() != 0) {
200 return true;
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());
220 return true;
222 report(
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();
231 return true;
234 if (!methodDecl->doesThisDeclarationHaveABody()
235 || methodDecl->isLateTemplateParsed())
237 return true;
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)
246 return true;
248 if (hasMultipleBaseInstances(
249 methodDecl->getParent(),
250 (*methodDecl->begin_overridden_methods())->getParent()))
252 return true;
256 const CXXMethodDecl* overriddenMethodDecl = findOverriddenOrSimilarMethodInSuperclasses(methodDecl);
257 if (!overriddenMethodDecl) {
258 return true;
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)
270 return true;
274 if (methodDecl->getReturnType().getCanonicalType()
275 != overriddenMethodDecl->getReturnType().getCanonicalType())
277 return true;
280 //TODO: check for identical exception specifications
282 const CompoundStmt* compoundStmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
283 if (!compoundStmt || compoundStmt->size() != 1)
284 return true;
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());
293 else
295 auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin());
296 if (returnStmt == nullptr) {
297 return true;
299 auto returnExpr = returnStmt->getRetValue();
300 if (returnExpr == nullptr) {
301 return true;
303 returnExpr = returnExpr->IgnoreImplicit();
305 // In something like
307 // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery(
308 // const rtl::OUString& sql)
309 // throw(SQLException, RuntimeException, std::exception)
310 // {
311 // return OCommonStatement::executeQuery( sql );
312 // }
314 // look down through all the
316 // ReturnStmt
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)
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 report(
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();
374 report(
375 DiagnosticsEngine::Note,
376 "method declaration here",
377 pOther->getLocation())
378 << pOther->getSourceRange();
380 return true;
383 const CXXMethodDecl* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl* methodDecl)
385 if (methodDecl->isVirtual()) {
386 return *methodDecl->begin_overridden_methods();
388 if (!methodDecl->getDeclName().isIdentifier()) {
389 return nullptr;
392 std::vector<const CXXMethodDecl*> maSimilarMethods;
394 auto BaseMatchesCallback = [&](const CXXBaseSpecifier *cxxBaseSpecifier, CXXBasePath& path)
396 if (cxxBaseSpecifier->getAccessSpecifier() != AS_public && cxxBaseSpecifier->getAccessSpecifier() != AS_protected)
397 return false;
398 if (!cxxBaseSpecifier->getType().getTypePtr())
399 return false;
400 const CXXRecordDecl* baseCXXRecordDecl = cxxBaseSpecifier->getType()->getAsCXXRecordDecl();
401 if (!baseCXXRecordDecl)
402 return false;
403 if (baseCXXRecordDecl->isInvalidDecl())
404 return false;
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())
411 continue;
412 if (!baseMethod->getDeclName().isIdentifier() || methodDecl->getName() != baseMethod->getName()) {
413 continue;
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())
422 continue;
424 if (methodDecl->getReturnType().getCanonicalType()
425 != baseMethod->getReturnType().getCanonicalType())
427 continue;
429 if (methodDecl->getNumParams() != baseMethod->getNumParams())
430 continue;
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;
437 break;
440 if (bParamsMatch)
441 maSimilarMethods.push_back(baseMethod);
443 return false;
446 CXXBasePaths aPaths;
447 methodDecl->getParent()->lookupInBases(BaseMatchesCallback, aPaths);
449 if (maSimilarMethods.size() == 1) {
450 return maSimilarMethods[0];
452 return nullptr;
456 loplugin::Plugin::Registration< UnnecessaryOverride > X("unnecessaryoverride", true);
460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */