1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #ifndef LO_CLANG_SHARED_PLUGINS
11 
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <set>
17 
18 #include <clang/AST/CXXInheritance.h>
19 #include "plugin.hxx"
20 
21 /**
22 look for methods where all they do is call their superclass method
23 */
24 
25 namespace {
26 
hasMultipleBaseInstances_(CXXRecordDecl const * derived,CXXRecordDecl const * canonicBase,bool & hasAsNonVirtualBase,bool & hasAsVirtualBase)27 bool hasMultipleBaseInstances_(
28     CXXRecordDecl const * derived, CXXRecordDecl const * canonicBase,
29     bool & hasAsNonVirtualBase, bool & hasAsVirtualBase)
30 {
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;
37         }
38         if (cls->getCanonicalDecl() == canonicBase) {
39             if (i->isVirtual()) {
40                 if (hasAsNonVirtualBase) {
41                     return true;
42                 }
43                 hasAsVirtualBase = true;
44             } else {
45                 if (hasAsNonVirtualBase || hasAsVirtualBase) {
46                     return true;
47                 }
48                 hasAsNonVirtualBase = true;
49             }
50         } else if (hasMultipleBaseInstances_(
51                        cls, canonicBase, hasAsNonVirtualBase, hasAsVirtualBase))
52         {
53             return true;
54         }
55     }
56     return false;
57 }
58 
hasMultipleBaseInstances(CXXRecordDecl const * derived,CXXRecordDecl const * base)59 bool hasMultipleBaseInstances(
60     CXXRecordDecl const * derived, CXXRecordDecl const * base)
61 {
62     bool nonVirt = false;
63     bool virt = false;
64     return hasMultipleBaseInstances_(
65         derived, base->getCanonicalDecl(), nonVirt, virt);
66 }
67 
68 class UnnecessaryOverride:
69     public loplugin::FilteringPlugin<UnnecessaryOverride>
70 {
71 public:
UnnecessaryOverride(loplugin::InstantiationData const & data)72     explicit UnnecessaryOverride(loplugin::InstantiationData const & data): FilteringPlugin(data) {}
73 
preRun()74     virtual bool preRun() override
75     {
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;
98     }
99 
run()100     virtual void run() override
101     {
102         if( preRun())
103             TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
104     }
105 
106     bool VisitCXXMethodDecl(const CXXMethodDecl *);
107 
108 private:
109     const CXXMethodDecl * findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl *);
110     bool BaseCheckCallback(const CXXRecordDecl *BaseDefinition);
111     CXXMemberCallExpr const * extractCallExpr(Expr const *);
112 };
113 
VisitCXXMethodDecl(const CXXMethodDecl * methodDecl)114 bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
115 {
116     if (ignoreLocation(methodDecl->getCanonicalDecl())) {
117         return true;
118     }
119     if (isa<CXXConstructorDecl>(methodDecl)) {
120         return true;
121     }
122 
123     StringRef aFileName = getFilenameOfLocation(
124         compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(methodDecl)));
125 
126     if (isa<CXXDestructorDecl>(methodDecl)
127        && !isInUnoIncludeFile(methodDecl))
128     {
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;
132 
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)
161         {
162             return true;
163         }
164         if (!compiler.getSourceManager().isInMainFile(
165                 methodDecl->getCanonicalDecl()->getLocation())
166             && !( methodDecl->isInlined()))
167         {
168             return true;
169         }
170         // if it's virtual, but it has a base-class with a non-virtual destructor
171         if (methodDecl->isVirtual())
172         {
173             bool baseWithVirtualDtor = false;
174             for (auto baseSpecifier = cls->bases_begin(); baseSpecifier != cls->bases_end(); ++baseSpecifier)
175             {
176                 const RecordType* baseRecordType = baseSpecifier->getType()->getAs<RecordType>();
177                 if (baseRecordType)
178                 {
179                     const CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(baseRecordType->getDecl());
180                     if (baseRecordDecl && baseRecordDecl->getDestructor()
181                         && baseRecordDecl->getDestructor()->isVirtual())
182                     {
183                         baseWithVirtualDtor = true;
184                         break;
185                     }
186                 }
187             }
188             if (!baseWithVirtualDtor)
189             {
190                 return true;
191             }
192         }
193         // corner case
194         if (methodDecl->isInlined()
195             && compiler.getSourceManager().isInMainFile(methodDecl->getLocation())
196             && !compiler.getSourceManager().isInMainFile(methodDecl->getCanonicalDecl()->getLocation()))
197         {
198             return true;
199         }
200         if (!methodDecl->isExplicitlyDefaulted()) {
201             if (!methodDecl->doesThisDeclarationHaveABody()
202                 || methodDecl->isLateTemplateParsed())
203             {
204                 return true;
205             }
206             auto stmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
207             if (stmt == nullptr || stmt->size() != 0) {
208                 return true;
209             }
210         }
211         //TODO: exception specification
212         if (!(cls->hasUserDeclaredCopyConstructor()
213               || cls->hasUserDeclaredCopyAssignment()
214               || cls->hasUserDeclaredMoveConstructor()
215               || cls->hasUserDeclaredMoveAssignment()))
216         {
217         }
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())))
226         {
227             report(DiagnosticsEngine::Fatal, "TODO", methodDecl->getLocation());
228             return true;
229         }
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();
238         }
239         return true;
240     }
241 
242     if (!methodDecl->doesThisDeclarationHaveABody()
243         || methodDecl->isLateTemplateParsed())
244     {
245         return true;
246     }
247 
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)
253         {
254             return true;
255         }
256         if (hasMultipleBaseInstances(
257                 methodDecl->getParent(),
258                 (*methodDecl->begin_overridden_methods())->getParent()))
259         {
260             return true;
261         }
262     }
263 
264     const CXXMethodDecl* overriddenMethodDecl = findOverriddenOrSimilarMethodInSuperclasses(methodDecl);
265     if (!overriddenMethodDecl) {
266         return true;
267     }
268 
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)
277         {
278             return true;
279         }
280     }
281 
282     if (methodDecl->getReturnType().getCanonicalType()
283         != overriddenMethodDecl->getReturnType().getCanonicalType())
284     {
285         return true;
286     }
287 
288     //TODO: check for identical exception specifications
289 
290     const CompoundStmt* compoundStmt = dyn_cast<CompoundStmt>(methodDecl->getBody());
291     if (!compoundStmt || compoundStmt->size() > 2)
292         return true;
293 
294     const CXXMemberCallExpr* callExpr = nullptr;
295     if (compoundStmt->size() == 1)
296     {
297         if (methodDecl->getReturnType().getCanonicalType()->isVoidType())
298         {
299             if (auto const e = dyn_cast<Expr>(*compoundStmt->body_begin())) {
300                 callExpr = dyn_cast<CXXMemberCallExpr>(e->IgnoreImplicit()->IgnoreParens());
301             }
302         }
303         else
304         {
305             auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin());
306             if (returnStmt == nullptr) {
307                 return true;
308             }
309             auto returnExpr = returnStmt->getRetValue();
310             if (returnExpr == nullptr) {
311                 return true;
312             }
313             callExpr = extractCallExpr(returnExpr);
314         }
315     }
316     else if (!methodDecl->getReturnType().getCanonicalType()->isVoidType())
317     {
318         /** handle constructions like
319                bool foo() {
320                 bool ret = Base::foo();
321                 return ret;
322             }
323         */
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();
339         }
340         if (!isa<DeclRefExpr>(retValue))
341             return true;
342         callExpr = extractCallExpr(varDecl->getInit());
343     }
344 
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();
358             }
359         }
360         const DeclRefExpr * declRefExpr = dyn_cast<DeclRefExpr>(e);
361         if (!declRefExpr || declRefExpr->getDecl() != methodDecl->getParamDecl(i))
362             return true;
363     }
364 
365     const CXXMethodDecl* pOther = nullptr;
366     if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation())
367         pOther = methodDecl->getCanonicalDecl();
368 
369     if (pOther) {
370         StringRef aFileName = getFilenameOfLocation(
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;
375     }
376 
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();
391     }
392     return true;
393 }
394 
findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl * methodDecl)395 const CXXMethodDecl* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl* methodDecl)
396 {
397     if (methodDecl->isVirtual()) {
398         return *methodDecl->begin_overridden_methods();
399     }
400     if (!methodDecl->getDeclName().isIdentifier()) {
401         return nullptr;
402     }
403 
404     const CXXMethodDecl* similarMethod = nullptr;
405     CXXBasePath similarBasePath;
406 
407     auto BaseMatchesCallback = [&](const CXXBaseSpecifier *cxxBaseSpecifier, CXXBasePath& path)
408     {
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())
419         {
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;
427             }
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())
434             {
435                 continue;
436             }
437             if (methodDecl->getReturnType().getCanonicalType()
438                 != baseMethod->getReturnType().getCanonicalType())
439             {
440                 continue;
441             }
442             if (methodDecl->getNumParams() != baseMethod->getNumParams())
443                 continue;
444             bool bParamsMatch = true;
445             for (unsigned i=0; i<methodDecl->param_size(); ++i)
446             {
447                 if (methodDecl->parameters()[i]->getType() != baseMethod->parameters()[i]->getType())
448                 {
449                     bParamsMatch = false;
450                     break;
451                 }
452             }
453             if (bParamsMatch)
454             {
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)
457                 {
458                     return lhs.Class == rhs.Class;
459                 };
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;
469             }
470         }
471         return false;
472     };
473 
474     CXXBasePaths aPaths;
475     if (methodDecl->getParent()->lookupInBases(BaseMatchesCallback, aPaths))
476         return nullptr;
477 
478     return similarMethod;
479 }
480 
extractCallExpr(Expr const * returnExpr)481 CXXMemberCallExpr const * UnnecessaryOverride::extractCallExpr(Expr const *returnExpr)
482 {
483     returnExpr = returnExpr->IgnoreImplicit();
484 
485     // In something like
486     //
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     //  }
493     //
494     // look down through all the
495     //
496     //   ReturnStmt
497     //   `-ExprWithCleanups
498     //     `-CXXConstructExpr
499     //      `-MaterializeTemporaryExpr
500     //       `-ImplicitCastExpr
501     //        `-CXXBindTemporaryExpr
502     //         `-CXXMemberCallExpr
503     //
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))
512             {
513                 returnExpr = tempExpr2->getSubExpr();
514             }
515             else if (auto tempExpr2 = dyn_cast<CXXMemberCallExpr>(tempExpr1))
516             {
517                 returnExpr = tempExpr2;
518             }
519         }
520     }
521 
522     return dyn_cast<CXXMemberCallExpr>(returnExpr->IgnoreParenImpCasts());
523 }
524 
525 loplugin::Plugin::Registration< UnnecessaryOverride > unnecessaryoverride("unnecessaryoverride", true);
526 
527 }
528 
529 #endif // LO_CLANG_SHARED_PLUGINS
530 
531 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
532