LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / compilerplugins / clang / unnecessaryoverride.cxx
blob7e7d39a0a5d836b5176bec21cbe7ccdf340a9c2c
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"
20 #include "check.hxx"
22 /**
23 look for methods where all they do is call their superclass method
26 namespace {
28 bool hasMultipleBaseInstances_(
29 CXXRecordDecl const * derived, CXXRecordDecl const * canonicBase,
30 bool & hasAsNonVirtualBase, bool & hasAsVirtualBase)
32 for (auto i = derived->bases_begin(); i != derived->bases_end(); ++i) {
33 auto const cls = i->getType()->getAsCXXRecordDecl();
34 if (cls == nullptr) {
35 assert(i->getType()->isDependentType());
36 // Conservatively assume "yes" for dependent bases:
37 return true;
39 if (cls->getCanonicalDecl() == canonicBase) {
40 if (i->isVirtual()) {
41 if (hasAsNonVirtualBase) {
42 return true;
44 hasAsVirtualBase = true;
45 } else {
46 if (hasAsNonVirtualBase || hasAsVirtualBase) {
47 return true;
49 hasAsNonVirtualBase = true;
51 } else if (hasMultipleBaseInstances_(
52 cls, canonicBase, hasAsNonVirtualBase, hasAsVirtualBase))
54 return true;
57 return false;
60 bool hasMultipleBaseInstances(
61 CXXRecordDecl const * derived, CXXRecordDecl const * base)
63 bool nonVirt = false;
64 bool virt = false;
65 return hasMultipleBaseInstances_(
66 derived, base->getCanonicalDecl(), nonVirt, virt);
69 class UnnecessaryOverride:
70 public loplugin::FilteringPlugin<UnnecessaryOverride>
72 public:
73 explicit UnnecessaryOverride(loplugin::InstantiationData const & data): FilteringPlugin(data) {}
75 virtual bool preRun() override
77 // ignore some files with problematic macros
78 StringRef fn(handler.getMainFileName());
79 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/framework/factories/ChildWindowPane.cxx"))
80 return false;
81 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Date.cxx"))
82 return false;
83 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/Time.cxx"))
84 return false;
85 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/hyperdlg.cxx"))
86 return false;
87 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/dialog/rubydialog.cxx"))
88 return false;
89 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/"))
90 return false;
91 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/view/spelldialog.cxx"))
92 return false;
93 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/dlg/SpellDialogChildWindow.cxx"))
94 return false;
95 // HAVE_ODBC_ADMINISTRATION
96 if (loplugin::isSamePathname(fn, SRCDIR "/dbaccess/source/ui/dlg/dsselect.cxx"))
97 return false;
98 if (loplugin::isSamePathname(fn, SRCDIR "/unotools/source/streaming/streamhelper.cxx"))
99 return false;
100 return true;
103 virtual void run() override
105 if( preRun())
106 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
109 bool VisitCXXMethodDecl(const CXXMethodDecl *);
111 private:
112 const CXXMethodDecl * findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl *);
113 bool BaseCheckCallback(const CXXRecordDecl *BaseDefinition);
114 CXXMemberCallExpr const * extractCallExpr(Expr const *);
117 bool UnnecessaryOverride::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
119 if (ignoreLocation(methodDecl->getCanonicalDecl())) {
120 return true;
122 if (isa<CXXConstructorDecl>(methodDecl)) {
123 return true;
126 StringRef aFileName = getFilenameOfLocation(
127 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(methodDecl)));
129 if (isa<CXXDestructorDecl>(methodDecl)
130 && !isInUnoIncludeFile(methodDecl))
132 // the code is this method is only __compiled__ if OSL_DEBUG_LEVEL > 1
133 if (loplugin::isSamePathname(aFileName, SRCDIR "/tools/source/stream/strmunx.cxx"))
134 return true;
136 // Warn about unnecessarily user-declared destructors.
137 // A destructor is deemed unnecessary if:
138 // * it is public;
139 // * its class is only defined in the .cxx file (i.e., the virtual
140 // destructor is neither used to control the place of vtable
141 // emission, nor is its definition depending on types that may still
142 // be incomplete);
143 // or
144 // the destructor is inline, the class definition is complete,
145 // and the class has no superclasses
146 // * it either does not have an explicit exception specification, or has
147 // a non-dependent explicit exception specification that is compatible
148 // with a non-dependent exception specification the destructor would
149 // have if it did not have an explicit one (TODO);
150 // * it is either defined as defaulted or with an empty body.
151 // Removing the user-declared destructor may cause the class to get an
152 // implicitly declared move constructor and/or move assignment operator;
153 // that is considered acceptable: If any subobject cannot be moved, the
154 // implicitly declared function will be defined as deleted (which is in
155 // practice not much different from not having it declared), and
156 // otherwise offering movability is likely even an improvement over not
157 // offering it due to a "pointless" user-declared destructor.
158 // Similarly, removing the user-declared destructor may cause the
159 // implicit definition of a copy constructor and/or copy assignment
160 // operator to change from being an obsolete feature to being a standard
161 // feature. That difference is not taken into account here.
162 auto cls = methodDecl->getParent();
163 if (methodDecl->getAccess() != AS_public)
165 return true;
167 if (!compiler.getSourceManager().isInMainFile(
168 methodDecl->getCanonicalDecl()->getLocation())
169 && !( methodDecl->isInlined()))
171 return true;
173 // if it's virtual, but it has a base-class with a non-virtual destructor
174 if (methodDecl->isVirtual())
176 bool baseWithVirtualDtor = false;
177 for (auto baseSpecifier = cls->bases_begin(); baseSpecifier != cls->bases_end(); ++baseSpecifier)
179 const RecordType* baseRecordType = baseSpecifier->getType()->getAs<RecordType>();
180 if (baseRecordType)
182 const CXXRecordDecl* baseRecordDecl = dyn_cast<CXXRecordDecl>(baseRecordType->getDecl());
183 if (baseRecordDecl && baseRecordDecl->getDestructor()
184 && baseRecordDecl->getDestructor()->isVirtual())
186 baseWithVirtualDtor = true;
187 break;
191 if (!baseWithVirtualDtor)
193 return true;
196 if (methodDecl->isExplicitlyDefaulted()) {
197 if (methodDecl->getPreviousDecl() != nullptr) {
198 return true;
200 } else {
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 if (bodyIt == compoundStmt->body_end()) {
326 return true;
328 auto declStmt = dyn_cast<DeclStmt>(*bodyIt);
329 if (!declStmt || !declStmt->isSingleDecl())
330 return true;
331 auto varDecl = dyn_cast<VarDecl>(declStmt->getSingleDecl());
332 ++bodyIt;
333 auto returnStmt = dyn_cast<ReturnStmt>(*bodyIt);
334 if (!varDecl || !returnStmt)
335 return true;
336 Expr const * retValue = returnStmt->getRetValue()->IgnoreParenImpCasts();
337 if (auto exprWithCleanups = dyn_cast<ExprWithCleanups>(retValue))
338 retValue = exprWithCleanups->getSubExpr()->IgnoreParenImpCasts();
339 if (auto constructExpr = dyn_cast<CXXConstructExpr>(retValue)) {
340 if (constructExpr->getNumArgs() == 1)
341 retValue = constructExpr->getArg(0)->IgnoreParenImpCasts();
343 if (!isa<DeclRefExpr>(retValue))
344 return true;
345 callExpr = extractCallExpr(varDecl->getInit());
348 if (!callExpr || callExpr->getMethodDecl() != overriddenMethodDecl)
349 return true;
350 const Expr* expr1 = callExpr->getImplicitObjectArgument()->IgnoreImpCasts();
351 if (!expr1)
352 return true;
353 const CXXThisExpr* expr2 = dyn_cast_or_null<CXXThisExpr>(expr1);
354 if (!expr2)
355 return true;
356 for (unsigned i = 0; i<callExpr->getNumArgs(); ++i) {
357 auto e = callExpr->getArg(i)->IgnoreImplicit();
358 if (auto const e1 = dyn_cast<CXXConstructExpr>(e)) {
359 if (e1->getConstructor()->isCopyOrMoveConstructor() && e1->getNumArgs() == 1) {
360 e = e1->getArg(0)->IgnoreImpCasts();
363 const DeclRefExpr * declRefExpr = dyn_cast<DeclRefExpr>(e);
364 if (!declRefExpr || declRefExpr->getDecl() != methodDecl->getParamDecl(i))
365 return true;
368 const CXXMethodDecl* pOther = nullptr;
369 if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation())
370 pOther = methodDecl->getCanonicalDecl();
372 if (pOther) {
373 StringRef aFileName = getFilenameOfLocation(
374 compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(pOther)));
375 // SFX_DECL_CHILDWINDOW_WITHID macro
376 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/sfx2/childwin.hxx"))
377 return true;
380 if (containsPreprocessingConditionalInclusion(methodDecl->getBody()->getSourceRange())) {
381 return true;
384 report(
385 DiagnosticsEngine::Warning, "%0%1 function just calls %2 parent",
386 methodDecl->getLocation())
387 << methodDecl->getAccess()
388 << (methodDecl->isVirtual() ? " virtual" : "")
389 << overriddenMethodDecl->getAccess()
390 << methodDecl->getSourceRange();
391 if (methodDecl->getCanonicalDecl()->getLocation() != methodDecl->getLocation()) {
392 const CXXMethodDecl* pOther = methodDecl->getCanonicalDecl();
393 report(
394 DiagnosticsEngine::Note,
395 "method declaration here",
396 pOther->getLocation())
397 << pOther->getSourceRange();
399 return true;
402 const CXXMethodDecl* UnnecessaryOverride::findOverriddenOrSimilarMethodInSuperclasses(const CXXMethodDecl* methodDecl)
404 if (methodDecl->isVirtual()) {
405 return *methodDecl->begin_overridden_methods();
407 if (!methodDecl->getDeclName().isIdentifier()) {
408 return nullptr;
411 const CXXMethodDecl* similarMethod = nullptr;
412 CXXBasePath similarBasePath;
414 auto BaseMatchesCallback = [&](const CXXBaseSpecifier *cxxBaseSpecifier, CXXBasePath& path)
416 if (cxxBaseSpecifier->getAccessSpecifier() != AS_public && cxxBaseSpecifier->getAccessSpecifier() != AS_protected)
417 return false;
418 if (!cxxBaseSpecifier->getType().getTypePtr())
419 return false;
420 const CXXRecordDecl* baseCXXRecordDecl = cxxBaseSpecifier->getType()->getAsCXXRecordDecl();
421 if (!baseCXXRecordDecl)
422 return false;
423 if (baseCXXRecordDecl->isInvalidDecl())
424 return false;
425 for (const CXXMethodDecl* baseMethod : baseCXXRecordDecl->methods())
427 auto effectiveBaseMethodAccess = baseMethod->getAccess();
428 if (effectiveBaseMethodAccess == AS_public && path.Access == AS_protected)
429 effectiveBaseMethodAccess = AS_protected;
430 if (effectiveBaseMethodAccess != methodDecl->getAccess())
431 continue;
432 if (!baseMethod->getDeclName().isIdentifier() || methodDecl->getName() != baseMethod->getName()) {
433 continue;
435 if (methodDecl->isStatic() != baseMethod->isStatic()
436 || methodDecl->isConst() != baseMethod->isConst()
437 || methodDecl->isVolatile() != baseMethod->isVolatile()
438 || (methodDecl->getRefQualifier()
439 != baseMethod->getRefQualifier())
440 || methodDecl->isVariadic() != baseMethod->isVariadic())
442 continue;
444 if (methodDecl->getReturnType().getCanonicalType()
445 != baseMethod->getReturnType().getCanonicalType())
447 continue;
449 if (methodDecl->getNumParams() != baseMethod->getNumParams())
450 continue;
451 bool bParamsMatch = true;
452 for (unsigned i=0; i<methodDecl->param_size(); ++i)
454 if (methodDecl->parameters()[i]->getType() != baseMethod->parameters()[i]->getType())
456 bParamsMatch = false;
457 break;
460 if (bParamsMatch)
462 // if we have already found a method directly below us in the inheritance hierarchy, just ignore this one
463 auto Compare = [&](CXXBasePathElement const & lhs, CXXBasePathElement const & rhs)
465 return lhs.Class == rhs.Class;
467 if (similarMethod
468 && similarBasePath.size() < path.size()
469 && std::equal(similarBasePath.begin(), similarBasePath.end(),
470 path.begin(), Compare))
471 break;
472 if (similarMethod)
473 return true; // short circuit the process
474 similarMethod = baseMethod;
475 similarBasePath = path;
478 return false;
481 CXXBasePaths aPaths;
482 if (methodDecl->getParent()->lookupInBases(BaseMatchesCallback, aPaths))
483 return nullptr;
485 return similarMethod;
488 CXXMemberCallExpr const * UnnecessaryOverride::extractCallExpr(Expr const *returnExpr)
490 returnExpr = returnExpr->IgnoreImplicit();
492 // In something like
494 // Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery(
495 // const rtl::OUString& sql)
496 // throw(SQLException, RuntimeException, std::exception)
497 // {
498 // return OCommonStatement::executeQuery( sql );
499 // }
501 // look down through all the
503 // ReturnStmt
504 // `-ExprWithCleanups
505 // `-CXXConstructExpr
506 // `-MaterializeTemporaryExpr
507 // `-ImplicitCastExpr
508 // `-CXXBindTemporaryExpr
509 // `-CXXMemberCallExpr
511 // where the fact that the overriding and overridden function have identical
512 // return types makes us confident that all we need to check here is whether
513 // there's an (arbitrary, one-argument) CXXConstructorExpr and
514 // CXXBindTemporaryExpr in between:
515 if (auto ctorExpr = dyn_cast<CXXConstructExpr>(returnExpr)) {
516 if (ctorExpr->getNumArgs() == 1) {
517 auto tempExpr1 = ctorExpr->getArg(0)->IgnoreImplicit();
518 if (auto tempExpr2 = dyn_cast<CXXBindTemporaryExpr>(tempExpr1))
520 returnExpr = tempExpr2->getSubExpr();
522 else if (auto tempExpr2 = dyn_cast<CXXMemberCallExpr>(tempExpr1))
524 returnExpr = tempExpr2;
529 return dyn_cast<CXXMemberCallExpr>(returnExpr->IgnoreParenImpCasts());
532 loplugin::Plugin::Registration< UnnecessaryOverride > unnecessaryoverride("unnecessaryoverride", true);
536 #endif // LO_CLANG_SHARED_PLUGINS
538 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */