bump product version to 7.2.5.1
[LibreOffice.git] / compilerplugins / clang / constmethod.cxx
blob45451152dc9953b7c887b51c64bbc263de93fb72
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 <algorithm>
11 #include <string>
12 #include <unordered_set>
13 #include <unordered_map>
14 #include <iostream>
16 #include "config_clang.h"
18 #include "plugin.hxx"
19 #include "compat.hxx"
20 #include "check.hxx"
21 #include "functionaddress.hxx"
23 #if CLANG_VERSION >= 110000
24 #include "clang/AST/ParentMapContext.h"
25 #endif
27 /**
28 Find methods that can be declared const.
30 This analysis attempts to implement "logical const" as opposed to "technical const", which means
31 we ignore always-const nature of std::unique_ptr::operator->
33 This is not a sophisticated analysis. It deliberately skips all of the hard cases for now.
34 It is an exercise in getting the most benefit for the least effort.
36 namespace
39 class ConstMethod:
40 public loplugin::FunctionAddress<loplugin::FilteringPlugin<ConstMethod>>
42 public:
43 explicit ConstMethod(loplugin::InstantiationData const & data): FunctionAddress(data) {}
45 virtual void run() override {
46 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
48 for (const CXXMethodDecl *pMethodDecl : interestingMethodSet) {
49 if (methodCannotBeConstSet.find(pMethodDecl) != methodCannotBeConstSet.end())
50 continue;
51 auto canonicalDecl = pMethodDecl->getCanonicalDecl();
52 if (getFunctionsWithAddressTaken().find((FunctionDecl const *)canonicalDecl)
53 != getFunctionsWithAddressTaken().end())
54 continue;
55 StringRef aFileName = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(canonicalDecl)));
56 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/LibreOfficeKit/LibreOfficeKit.hxx"))
57 continue;
58 report(
59 DiagnosticsEngine::Warning,
60 "this method can be const",
61 compat::getBeginLoc(pMethodDecl))
62 << pMethodDecl->getSourceRange();
63 if (canonicalDecl->getLocation() != pMethodDecl->getLocation()) {
64 report(
65 DiagnosticsEngine::Note,
66 "canonical method declaration here",
67 compat::getBeginLoc(canonicalDecl))
68 << canonicalDecl->getSourceRange();
73 bool TraverseCXXMethodDecl(CXXMethodDecl *);
74 bool TraverseCXXConversionDecl(CXXConversionDecl *);
75 bool VisitCXXMethodDecl(const CXXMethodDecl *);
76 bool VisitCXXThisExpr(const CXXThisExpr *);
78 private:
79 bool isPointerOrReferenceToConst(const QualType& qt);
80 bool isPointerOrReferenceToNonConst(const QualType& qt);
81 bool checkIfCanBeConst(const Stmt*, const CXXMethodDecl*);
83 std::unordered_set<const CXXMethodDecl*> interestingMethodSet;
84 std::unordered_set<const CXXMethodDecl*> methodCannotBeConstSet;
85 CXXMethodDecl const * currCXXMethodDecl;
88 bool ConstMethod::TraverseCXXMethodDecl(CXXMethodDecl * cxxMethodDecl)
90 currCXXMethodDecl = cxxMethodDecl;
91 bool rv = RecursiveASTVisitor<ConstMethod>::TraverseCXXMethodDecl(cxxMethodDecl);
92 currCXXMethodDecl = nullptr;
93 return rv;
96 bool ConstMethod::TraverseCXXConversionDecl(CXXConversionDecl * cxxConversionDecl)
98 currCXXMethodDecl = cxxConversionDecl;
99 bool rv = RecursiveASTVisitor<ConstMethod>::TraverseCXXConversionDecl(cxxConversionDecl);
100 currCXXMethodDecl = nullptr;
101 return rv;
104 bool ConstMethod::VisitCXXMethodDecl(const CXXMethodDecl * cxxMethodDecl)
106 if (ignoreLocation(cxxMethodDecl) || !cxxMethodDecl->isThisDeclarationADefinition()) {
107 return true;
109 if (cxxMethodDecl->isConst())
110 return true;
111 // ignore stuff that forms part of the stable URE interface
112 if (isInUnoIncludeFile(cxxMethodDecl)) {
113 return true;
115 // TODO ignore template stuff for now
116 if (cxxMethodDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) {
117 return true;
119 if (cxxMethodDecl->isDeleted())
120 return true;
121 if (cxxMethodDecl->isStatic())
122 return true;
123 if (cxxMethodDecl->isOverloadedOperator())
124 return true;
125 if (isa<CXXConstructorDecl>(cxxMethodDecl))
126 return true;
127 if (isa<CXXDestructorDecl>(cxxMethodDecl))
128 return true;
129 if (cxxMethodDecl->getParent()->getDescribedClassTemplate() != nullptr ) {
130 return true;
132 // ignore virtual methods
133 if (cxxMethodDecl->isVirtual() ) {
134 return true;
136 // ignore macro expansions so we can ignore the IMPL_LINK macros from include/tools/link.hxx
137 // TODO make this more precise
138 if (cxxMethodDecl->getLocation().isMacroID())
139 return true;
141 if (!cxxMethodDecl->getIdentifier())
142 return true;
143 if (cxxMethodDecl->getNumParams() > 0)
144 return true;
145 // returning pointers or refs to non-const stuff, and then having the whole method
146 // be const doesn't seem like a good idea
147 auto tc = loplugin::TypeCheck(cxxMethodDecl->getReturnType());
148 if (tc.Pointer().NonConst())
149 return true;
150 if (tc.LvalueReference().NonConst())
151 return true;
152 // a Get method that returns void is probably doing something that has side-effects
153 if (tc.Void())
154 return true;
156 StringRef name = cxxMethodDecl->getName();
157 if (!name.startswith("get") && !name.startswith("Get")
158 && !name.startswith("is") && !name.startswith("Is")
159 && !name.startswith("has") && !name.startswith("Has"))
160 return true;
162 // something lacking in my analysis here
163 if (loplugin::DeclCheck(cxxMethodDecl).Function("GetDescr").Class("SwRangeRedline").GlobalNamespace())
164 return true;
166 interestingMethodSet.insert(cxxMethodDecl);
168 return true;
171 bool ConstMethod::VisitCXXThisExpr( const CXXThisExpr* cxxThisExpr )
173 if (!currCXXMethodDecl)
174 return true;
175 if (ignoreLocation(cxxThisExpr))
176 return true;
177 // ignore stuff that forms part of the stable URE interface
178 if (isInUnoIncludeFile(compat::getBeginLoc(cxxThisExpr)))
179 return true;
180 if (interestingMethodSet.find(currCXXMethodDecl) == interestingMethodSet.end())
181 return true;
182 // no need to check again if we have already eliminated this one
183 if (methodCannotBeConstSet.find(currCXXMethodDecl) != methodCannotBeConstSet.end())
184 return true;
185 if (!checkIfCanBeConst(cxxThisExpr, currCXXMethodDecl))
186 methodCannotBeConstSet.insert(currCXXMethodDecl);
188 return true;
191 // Walk up from a statement that contains a CXXThisExpr, checking if the usage means that the
192 // related CXXMethodDecl can be const.
193 bool ConstMethod::checkIfCanBeConst(const Stmt* stmt, const CXXMethodDecl* cxxMethodDecl)
195 const Stmt* parent = getParentStmt( stmt );
196 if (!parent) {
197 auto parentsRange = compiler.getASTContext().getParents(*stmt);
198 if ( parentsRange.begin() == parentsRange.end())
199 return true;
200 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
201 if (!varDecl)
203 report(
204 DiagnosticsEngine::Warning,
205 "no parent?",
206 compat::getBeginLoc(stmt))
207 << stmt->getSourceRange();
208 return false;
210 return varDecl->getType()->isIntegralOrEnumerationType()
211 || loplugin::TypeCheck(varDecl->getType()).Pointer().Const()
212 || loplugin::TypeCheck(varDecl->getType()).LvalueReference().Const();
215 if (auto unaryOperator = dyn_cast<UnaryOperator>(parent)) {
216 UnaryOperator::Opcode op = unaryOperator->getOpcode();
217 if (op == UO_AddrOf || op == UO_PreInc || op == UO_PostInc
218 || op == UO_PreDec || op == UO_PostDec) {
219 return false;
221 if (op == UO_Deref) {
222 return checkIfCanBeConst(parent, cxxMethodDecl);
224 return true;
225 } else if (auto binaryOp = dyn_cast<BinaryOperator>(parent)) {
226 BinaryOperator::Opcode op = binaryOp->getOpcode();
227 if (binaryOp->getRHS() == stmt) {
228 return true;
230 if (op == BO_Assign || op == BO_PtrMemD || op == BO_PtrMemI || op == BO_MulAssign
231 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
232 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
233 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign) {
234 return false;
236 // // for pointer arithmetic need to check parent
237 // if (binaryOp->getType()->isPointerType()) {
238 // return checkIfCanBeConst(parent, cxxMethodDecl);
239 // }
240 return true;
241 } else if (auto constructExpr = dyn_cast<CXXConstructExpr>(parent)) {
242 const CXXConstructorDecl * constructorDecl = constructExpr->getConstructor();
243 for (unsigned i = 0; i < constructExpr->getNumArgs(); ++i) {
244 if (constructExpr->getArg(i) == stmt) {
245 return isPointerOrReferenceToConst(constructorDecl->getParamDecl(i)->getType());
248 return false; // TODO ??
249 } else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent)) {
250 const CXXMethodDecl* calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(operatorCallExpr->getDirectCallee());
251 if (calleeMethodDecl) {
252 // unary operator
253 if (calleeMethodDecl->getNumParams() == 0) {
254 // some classes like std::unique_ptr do not do a very good job with their operator-> which is always const
255 if (operatorCallExpr->getOperator() == OO_Arrow || operatorCallExpr->getOperator() == OO_Star) {
256 return checkIfCanBeConst(parent, cxxMethodDecl);
258 return calleeMethodDecl->isConst();
260 // some classes like std::unique_ptr do not do a very good job with their operator[] which is always const
261 if (calleeMethodDecl->getNumParams() == 1 && operatorCallExpr->getArg(0) == stmt) {
262 if (operatorCallExpr->getOperator() == OO_Subscript) {
263 return false;
266 // binary operator
267 if (operatorCallExpr->getArg(0) == stmt) {
268 return calleeMethodDecl->isConst();
270 unsigned const n = std::min(
271 operatorCallExpr->getNumArgs(),
272 calleeMethodDecl->getNumParams());
273 for (unsigned i = 1; i < n; ++i)
274 if (operatorCallExpr->getArg(i) == stmt) {
275 return isPointerOrReferenceToConst(calleeMethodDecl->getParamDecl(i - 1)->getType());
277 } else {
278 const Expr* callee = operatorCallExpr->getCallee()->IgnoreParenImpCasts();
279 const DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
280 const FunctionDecl* calleeFunctionDecl = nullptr;
281 if (dr) {
282 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
284 if (calleeFunctionDecl) {
285 for (unsigned i = 0; i < operatorCallExpr->getNumArgs(); ++i) {
286 if (operatorCallExpr->getArg(i) == stmt) {
287 return isPointerOrReferenceToConst(calleeFunctionDecl->getParamDecl(i)->getType());
292 return false; // TODO ???
293 } else if (auto callExpr = dyn_cast<CallExpr>(parent)) {
294 QualType functionType = callExpr->getCallee()->getType();
295 if (functionType->isFunctionPointerType()) {
296 functionType = functionType->getPointeeType();
298 if (const FunctionProtoType* prototype = functionType->getAs<FunctionProtoType>()) {
299 // TODO could do better
300 if (prototype->isVariadic()) {
301 return false;
303 if (callExpr->getCallee() == stmt) {
304 return true;
306 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
307 if (callExpr->getArg(i) == stmt) {
308 return isPointerOrReferenceToConst(prototype->getParamType(i));
312 const FunctionDecl* calleeFunctionDecl = callExpr->getDirectCallee();
313 if (calleeFunctionDecl)
315 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(parent)) {
316 const MemberExpr* memberExpr = dyn_cast<MemberExpr>(stmt);
317 if (memberExpr && memberCallExpr->getImplicitObjectArgument() == memberExpr->getBase())
319 const CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
320 // some classes like std::unique_ptr do not do a very good job with their get() which is always const
321 if (calleeMethodDecl->getIdentifier() && calleeMethodDecl->getName() == "get") {
322 return checkIfCanBeConst(parent, cxxMethodDecl);
324 // VclPtr<T>'s implicit conversion to T*
325 if (isa<CXXConversionDecl>(calleeMethodDecl)) {
326 if (loplugin::DeclCheck(calleeMethodDecl->getParent()).Class("OWeakObject").Namespace("cppu").GlobalNamespace())
327 return false;
328 return checkIfCanBeConst(parent, cxxMethodDecl);
330 return calleeMethodDecl->isConst();
333 // TODO could do better
334 if (calleeFunctionDecl->isVariadic()) {
335 return false;
337 if (callExpr->getCallee() == stmt) {
338 return true;
340 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
341 if (i >= calleeFunctionDecl->getNumParams()) // can happen in template code
342 return false;
343 if (callExpr->getArg(i) == stmt) {
344 return isPointerOrReferenceToConst(calleeFunctionDecl->getParamDecl(i)->getType());
348 return false; // TODO ????
349 // } else if (auto callExpr = dyn_cast<ObjCMessageExpr>(parent)) {
350 // if (callExpr->getInstanceReceiver() == stmt) {
351 // return true;
352 // }
353 // if (auto const method = callExpr->getMethodDecl()) {
354 // // TODO could do better
355 // if (method->isVariadic()) {
356 // return false;
357 // }
358 // assert(method->param_size() == callExpr->getNumArgs());
359 // for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
360 // if (callExpr->getArg(i) == stmt) {
361 // return isPointerOrReferenceToConst(
362 // method->param_begin()[i]->getType());
363 // }
364 // }
365 // }
366 // return false; // TODO ????
367 } else if (isa<CXXReinterpretCastExpr>(parent)) {
368 return false;
369 } else if (isa<ImplicitCastExpr>(parent)) {
370 return checkIfCanBeConst(parent, cxxMethodDecl);
371 } else if (isa<CXXStaticCastExpr>(parent)) {
372 return checkIfCanBeConst(parent, cxxMethodDecl);
373 } else if (isa<CXXDynamicCastExpr>(parent)) {
374 return checkIfCanBeConst(parent, cxxMethodDecl);
375 } else if (isa<CXXFunctionalCastExpr>(parent)) {
376 return checkIfCanBeConst(parent, cxxMethodDecl);
377 } else if (isa<CXXConstCastExpr>(parent)) {
378 return false;
379 } else if (isa<CStyleCastExpr>(parent)) {
380 return checkIfCanBeConst(parent, cxxMethodDecl);
381 // } else if (isa<CastExpr>(parent)) { // all other cast expression subtypes
382 // if (auto e = dyn_cast<ExplicitCastExpr>(parent)) {
383 // if (loplugin::TypeCheck(e->getTypeAsWritten()).Void()) {
384 // if (auto const sub = dyn_cast<DeclRefExpr>(
385 // e->getSubExpr()->IgnoreParenImpCasts()))
386 // {
387 // if (sub->getDecl() == cxxMethodDecl) {
388 // return false;
389 // }
390 // }
391 // }
392 // }
393 // return checkIfCanBeConst(parent, cxxMethodDecl);
394 } else if (isa<MemberExpr>(parent)) {
395 return checkIfCanBeConst(parent, cxxMethodDecl);
396 } else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent)) {
397 if (arraySubscriptExpr->getIdx() == stmt)
398 return true;
399 return checkIfCanBeConst(parent, cxxMethodDecl);
400 } else if (isa<ParenExpr>(parent)) {
401 return checkIfCanBeConst(parent, cxxMethodDecl);
402 } else if (auto declStmt = dyn_cast<DeclStmt>(parent)) {
403 for (Decl const * decl : declStmt->decls())
404 if (auto varDecl = dyn_cast<VarDecl>(decl)) {
405 if (varDecl->getInit() == stmt) {
406 auto tc = loplugin::TypeCheck(varDecl->getType());
407 if (tc.LvalueReference() && !tc.LvalueReference().Const())
408 return false;
409 if (tc.Pointer() && !tc.Pointer().Const())
410 return false;
411 return true;
414 // fall through
415 } else if (isa<ReturnStmt>(parent)) {
416 return !isPointerOrReferenceToNonConst(cxxMethodDecl->getReturnType());
417 } else if (isa<InitListExpr>(parent)) {
418 return false; // TODO could be improved
419 } else if (isa<IfStmt>(parent)) {
420 return true;
421 } else if (isa<WhileStmt>(parent)) {
422 return true;
423 } else if (isa<ForStmt>(parent)) {
424 return true;
425 } else if (isa<CompoundStmt>(parent)) {
426 return true;
427 } else if (isa<SwitchStmt>(parent)) {
428 return true;
429 } else if (isa<DoStmt>(parent)) {
430 return true;
431 } else if (isa<CXXDeleteExpr>(parent)) {
432 return false;
433 // } else if (isa<VAArgExpr>(parent)) {
434 // return false;
435 } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
436 return false;
437 } else if (isa<MaterializeTemporaryExpr>(parent)) {
438 return checkIfCanBeConst(parent, cxxMethodDecl);
439 } else if (auto conditionalExpr = dyn_cast<ConditionalOperator>(parent)) {
440 if (conditionalExpr->getCond() == stmt)
441 return true;
442 return checkIfCanBeConst(parent, cxxMethodDecl);
443 // } else if (isa<UnaryExprOrTypeTraitExpr>(parent)) {
444 // return false; // ???
445 } else if (isa<CXXNewExpr>(parent)) {
446 // for (auto pa : cxxNewExpr->placement_arguments())
447 // if (pa == stmt)
448 // return false;
449 return true; // because the Stmt must be a parameter to the expression, probably an array length
450 // } else if (auto lambdaExpr = dyn_cast<LambdaExpr>(parent)) {
451 //// for (auto it = lambdaExpr->capture_begin(); it != lambdaExpr->capture_end(); ++it)
452 //// {
453 //// if (it->capturesVariable() && it->getCapturedVar() == cxxMethodDecl)
454 //// return it->getCaptureKind() != LCK_ByRef;
455 //// }
456 // return true;
457 // } else if (isa<CXXTypeidExpr>(parent)) {
458 // return true;
459 } else if (isa<ParenListExpr>(parent)) {
460 return true;
461 } else if (isa<CXXUnresolvedConstructExpr>(parent)) {
462 return false;
463 // } else if (isa<UnresolvedMemberExpr>(parent)) {
464 // return false;
465 // } else if (isa<PackExpansionExpr>(parent)) {
466 // return false;
467 } else if (isa<ExprWithCleanups>(parent)) {
468 return checkIfCanBeConst(parent, cxxMethodDecl);
469 // } else if (isa<CaseStmt>(parent)) {
470 // return true;
471 // } else if (isa<CXXPseudoDestructorExpr>(parent)) {
472 // return false;
473 // } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
474 // return false;
475 // } else if (isa<ObjCIvarRefExpr>(parent)) {
476 // return checkIfCanBeConst(parent, cxxMethodDecl);
477 } else if (isa<CXXTemporaryObjectExpr>(parent)) {
478 return true;
479 } else if (isa<CXXBindTemporaryExpr>(parent)) {
480 return true;
482 if (parent)
483 parent->dump();
484 // if (cxxMethodDecl)
485 // cxxMethodDecl->dump();
486 report(
487 DiagnosticsEngine::Warning,
488 "oh dear, what can the matter be?",
489 compat::getBeginLoc(parent))
490 << parent->getSourceRange();
491 return false;
494 bool ConstMethod::isPointerOrReferenceToConst(const QualType& qt) {
495 auto const type = loplugin::TypeCheck(qt);
496 if (type.Pointer()) {
497 return bool(type.Pointer().Const());
498 } else if (type.LvalueReference()) {
499 return bool(type.LvalueReference().Const());
501 return false;
504 bool ConstMethod::isPointerOrReferenceToNonConst(const QualType& qt) {
505 auto const type = loplugin::TypeCheck(qt);
506 if (type.Pointer()) {
507 return !bool(type.Pointer().Const());
508 } else if (type.LvalueReference()) {
509 return !bool(type.LvalueReference().Const());
511 return false;
514 loplugin::Plugin::Registration< ConstMethod > X("constmethod", false);
518 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */