1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
12 #include <unordered_set>
13 #include <unordered_map>
19 #include "functionaddress.hxx"
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.
34 public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<ConstMethod
>>
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())
45 auto canonicalDecl
= pMethodDecl
->getCanonicalDecl();
46 if (getFunctionsWithAddressTaken().find((FunctionDecl
const *)canonicalDecl
)
47 != getFunctionsWithAddressTaken().end())
49 StringRef aFileName
= getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(canonicalDecl
)));
50 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/LibreOfficeKit/LibreOfficeKit.hxx"))
53 DiagnosticsEngine::Warning
,
54 "this method can be const",
55 compat::getBeginLoc(pMethodDecl
))
56 << pMethodDecl
->getSourceRange();
57 if (canonicalDecl
->getLocation() != pMethodDecl
->getLocation()) {
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
*);
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;
90 bool ConstMethod::TraverseCXXConversionDecl(CXXConversionDecl
* cxxConversionDecl
)
92 currCXXMethodDecl
= cxxConversionDecl
;
93 bool rv
= RecursiveASTVisitor
<ConstMethod
>::TraverseCXXConversionDecl(cxxConversionDecl
);
94 currCXXMethodDecl
= nullptr;
98 bool ConstMethod::VisitCXXMethodDecl(const CXXMethodDecl
* cxxMethodDecl
)
100 if (ignoreLocation(cxxMethodDecl
) || !cxxMethodDecl
->isThisDeclarationADefinition()) {
103 if (cxxMethodDecl
->isConst())
105 // ignore stuff that forms part of the stable URE interface
106 if (isInUnoIncludeFile(cxxMethodDecl
)) {
109 // TODO ignore template stuff for now
110 if (cxxMethodDecl
->getTemplatedKind() != FunctionDecl::TK_NonTemplate
) {
113 if (cxxMethodDecl
->isDeleted())
115 if (cxxMethodDecl
->isStatic())
117 if (cxxMethodDecl
->isOverloadedOperator())
119 if (isa
<CXXConstructorDecl
>(cxxMethodDecl
))
121 if (isa
<CXXDestructorDecl
>(cxxMethodDecl
))
123 if (cxxMethodDecl
->getParent()->getDescribedClassTemplate() != nullptr ) {
126 // ignore virtual methods
127 if (cxxMethodDecl
->isVirtual() ) {
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())
135 if (!cxxMethodDecl
->getIdentifier())
137 if (cxxMethodDecl
->getNumParams() > 0)
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())
144 if (tc
.LvalueReference().NonConst())
146 // a Get method that returns void is probably doing something that has side-effects
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"))
156 // something lacking in my analysis here
157 if (loplugin::DeclCheck(cxxMethodDecl
).Function("GetDescr").Class("SwRangeRedline").GlobalNamespace())
160 interestingMethodSet
.insert(cxxMethodDecl
);
165 bool ConstMethod::VisitCXXThisExpr( const CXXThisExpr
* cxxThisExpr
)
167 if (!currCXXMethodDecl
)
169 if (ignoreLocation(cxxThisExpr
))
171 // ignore stuff that forms part of the stable URE interface
172 if (isInUnoIncludeFile(compat::getBeginLoc(cxxThisExpr
)))
174 if (interestingMethodSet
.find(currCXXMethodDecl
) == interestingMethodSet
.end())
176 // no need to check again if we have already eliminated this one
177 if (methodCannotBeConstSet
.find(currCXXMethodDecl
) != methodCannotBeConstSet
.end())
179 if (!checkIfCanBeConst(cxxThisExpr
, currCXXMethodDecl
))
180 methodCannotBeConstSet
.insert(currCXXMethodDecl
);
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
);
191 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
192 if ( parentsRange
.begin() == parentsRange
.end())
194 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
198 DiagnosticsEngine::Warning
,
200 compat::getBeginLoc(stmt
))
201 << stmt
->getSourceRange();
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
) {
215 if (op
== UO_Deref
) {
216 return checkIfCanBeConst(parent
, cxxMethodDecl
);
219 } else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
)) {
220 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
221 if (binaryOp
->getRHS() == stmt
) {
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
) {
230 // // for pointer arithmetic need to check parent
231 // if (binaryOp->getType()->isPointerType()) {
232 // return checkIfCanBeConst(parent, cxxMethodDecl);
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
) {
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
) {
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());
272 const Expr
* callee
= operatorCallExpr
->getCallee()->IgnoreParenImpCasts();
273 const DeclRefExpr
* dr
= dyn_cast
<DeclRefExpr
>(callee
);
274 const FunctionDecl
* calleeFunctionDecl
= nullptr;
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()) {
297 if (callExpr
->getCallee() == stmt
) {
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())
322 return checkIfCanBeConst(parent
, cxxMethodDecl
);
324 return calleeMethodDecl
->isConst();
327 // TODO could do better
328 if (calleeFunctionDecl
->isVariadic()) {
331 if (callExpr
->getCallee() == stmt
) {
334 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
335 if (i
>= calleeFunctionDecl
->getNumParams()) // can happen in template code
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) {
347 // if (auto const method = callExpr->getMethodDecl()) {
348 // // TODO could do better
349 // if (method->isVariadic()) {
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());
360 // return false; // TODO ????
361 } else if (isa
<CXXReinterpretCastExpr
>(parent
)) {
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
)) {
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()))
381 // if (sub->getDecl() == cxxMethodDecl) {
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
)
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())
403 if (tc
.Pointer() && !tc
.Pointer().Const())
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
)) {
415 } else if (isa
<WhileStmt
>(parent
)) {
417 } else if (isa
<ForStmt
>(parent
)) {
419 } else if (isa
<CompoundStmt
>(parent
)) {
421 } else if (isa
<SwitchStmt
>(parent
)) {
423 } else if (isa
<DoStmt
>(parent
)) {
425 } else if (isa
<CXXDeleteExpr
>(parent
)) {
427 // } else if (isa<VAArgExpr>(parent)) {
429 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
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
)
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())
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)
447 //// if (it->capturesVariable() && it->getCapturedVar() == cxxMethodDecl)
448 //// return it->getCaptureKind() != LCK_ByRef;
451 // } else if (isa<CXXTypeidExpr>(parent)) {
453 } else if (isa
<ParenListExpr
>(parent
)) {
455 } else if (isa
<CXXUnresolvedConstructExpr
>(parent
)) {
457 // } else if (isa<UnresolvedMemberExpr>(parent)) {
459 // } else if (isa<PackExpansionExpr>(parent)) {
461 } else if (isa
<ExprWithCleanups
>(parent
)) {
462 return checkIfCanBeConst(parent
, cxxMethodDecl
);
463 // } else if (isa<CaseStmt>(parent)) {
465 // } else if (isa<CXXPseudoDestructorExpr>(parent)) {
467 // } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
469 // } else if (isa<ObjCIvarRefExpr>(parent)) {
470 // return checkIfCanBeConst(parent, cxxMethodDecl);
471 } else if (isa
<CXXTemporaryObjectExpr
>(parent
)) {
473 } else if (isa
<CXXBindTemporaryExpr
>(parent
)) {
478 // if (cxxMethodDecl)
479 // cxxMethodDecl->dump();
481 DiagnosticsEngine::Warning
,
482 "oh dear, what can the matter be?",
483 compat::getBeginLoc(parent
))
484 << parent
->getSourceRange();
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());
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());
508 loplugin::Plugin::Registration
< ConstMethod
> X("constmethod", false);
512 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */