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