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>
16 #include "config_clang.h"
21 #include "functionaddress.hxx"
23 #if CLANG_VERSION >= 110000
24 #include "clang/AST/ParentMapContext.h"
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.
40 public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<ConstMethod
>>
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())
51 auto canonicalDecl
= pMethodDecl
->getCanonicalDecl();
52 if (getFunctionsWithAddressTaken().find((FunctionDecl
const *)canonicalDecl
)
53 != getFunctionsWithAddressTaken().end())
55 StringRef aFileName
= getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(compat::getBeginLoc(canonicalDecl
)));
56 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/LibreOfficeKit/LibreOfficeKit.hxx"))
59 DiagnosticsEngine::Warning
,
60 "this method can be const",
61 compat::getBeginLoc(pMethodDecl
))
62 << pMethodDecl
->getSourceRange();
63 if (canonicalDecl
->getLocation() != pMethodDecl
->getLocation()) {
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
*);
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;
96 bool ConstMethod::TraverseCXXConversionDecl(CXXConversionDecl
* cxxConversionDecl
)
98 currCXXMethodDecl
= cxxConversionDecl
;
99 bool rv
= RecursiveASTVisitor
<ConstMethod
>::TraverseCXXConversionDecl(cxxConversionDecl
);
100 currCXXMethodDecl
= nullptr;
104 bool ConstMethod::VisitCXXMethodDecl(const CXXMethodDecl
* cxxMethodDecl
)
106 if (ignoreLocation(cxxMethodDecl
) || !cxxMethodDecl
->isThisDeclarationADefinition()) {
109 if (cxxMethodDecl
->isConst())
111 // ignore stuff that forms part of the stable URE interface
112 if (isInUnoIncludeFile(cxxMethodDecl
)) {
115 // TODO ignore template stuff for now
116 if (cxxMethodDecl
->getTemplatedKind() != FunctionDecl::TK_NonTemplate
) {
119 if (cxxMethodDecl
->isDeleted())
121 if (cxxMethodDecl
->isStatic())
123 if (cxxMethodDecl
->isOverloadedOperator())
125 if (isa
<CXXConstructorDecl
>(cxxMethodDecl
))
127 if (isa
<CXXDestructorDecl
>(cxxMethodDecl
))
129 if (cxxMethodDecl
->getParent()->getDescribedClassTemplate() != nullptr ) {
132 // ignore virtual methods
133 if (cxxMethodDecl
->isVirtual() ) {
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())
141 if (!cxxMethodDecl
->getIdentifier())
143 if (cxxMethodDecl
->getNumParams() > 0)
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())
150 if (tc
.LvalueReference().NonConst())
152 // a Get method that returns void is probably doing something that has side-effects
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"))
162 // something lacking in my analysis here
163 if (loplugin::DeclCheck(cxxMethodDecl
).Function("GetDescr").Class("SwRangeRedline").GlobalNamespace())
166 interestingMethodSet
.insert(cxxMethodDecl
);
171 bool ConstMethod::VisitCXXThisExpr( const CXXThisExpr
* cxxThisExpr
)
173 if (!currCXXMethodDecl
)
175 if (ignoreLocation(cxxThisExpr
))
177 // ignore stuff that forms part of the stable URE interface
178 if (isInUnoIncludeFile(compat::getBeginLoc(cxxThisExpr
)))
180 if (interestingMethodSet
.find(currCXXMethodDecl
) == interestingMethodSet
.end())
182 // no need to check again if we have already eliminated this one
183 if (methodCannotBeConstSet
.find(currCXXMethodDecl
) != methodCannotBeConstSet
.end())
185 if (!checkIfCanBeConst(cxxThisExpr
, currCXXMethodDecl
))
186 methodCannotBeConstSet
.insert(currCXXMethodDecl
);
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
);
197 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
198 if ( parentsRange
.begin() == parentsRange
.end())
200 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
204 DiagnosticsEngine::Warning
,
206 compat::getBeginLoc(stmt
))
207 << stmt
->getSourceRange();
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
) {
221 if (op
== UO_Deref
) {
222 return checkIfCanBeConst(parent
, cxxMethodDecl
);
225 } else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
)) {
226 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
227 if (binaryOp
->getRHS() == stmt
) {
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
) {
236 // // for pointer arithmetic need to check parent
237 // if (binaryOp->getType()->isPointerType()) {
238 // return checkIfCanBeConst(parent, cxxMethodDecl);
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
) {
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
) {
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());
278 const Expr
* callee
= operatorCallExpr
->getCallee()->IgnoreParenImpCasts();
279 const DeclRefExpr
* dr
= dyn_cast
<DeclRefExpr
>(callee
);
280 const FunctionDecl
* calleeFunctionDecl
= nullptr;
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()) {
303 if (callExpr
->getCallee() == stmt
) {
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())
328 return checkIfCanBeConst(parent
, cxxMethodDecl
);
330 return calleeMethodDecl
->isConst();
333 // TODO could do better
334 if (calleeFunctionDecl
->isVariadic()) {
337 if (callExpr
->getCallee() == stmt
) {
340 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
341 if (i
>= calleeFunctionDecl
->getNumParams()) // can happen in template code
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) {
353 // if (auto const method = callExpr->getMethodDecl()) {
354 // // TODO could do better
355 // if (method->isVariadic()) {
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());
366 // return false; // TODO ????
367 } else if (isa
<CXXReinterpretCastExpr
>(parent
)) {
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
)) {
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()))
387 // if (sub->getDecl() == cxxMethodDecl) {
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
)
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())
409 if (tc
.Pointer() && !tc
.Pointer().Const())
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
)) {
421 } else if (isa
<WhileStmt
>(parent
)) {
423 } else if (isa
<ForStmt
>(parent
)) {
425 } else if (isa
<CompoundStmt
>(parent
)) {
427 } else if (isa
<SwitchStmt
>(parent
)) {
429 } else if (isa
<DoStmt
>(parent
)) {
431 } else if (isa
<CXXDeleteExpr
>(parent
)) {
433 // } else if (isa<VAArgExpr>(parent)) {
435 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
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
)
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())
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)
453 //// if (it->capturesVariable() && it->getCapturedVar() == cxxMethodDecl)
454 //// return it->getCaptureKind() != LCK_ByRef;
457 // } else if (isa<CXXTypeidExpr>(parent)) {
459 } else if (isa
<ParenListExpr
>(parent
)) {
461 } else if (isa
<CXXUnresolvedConstructExpr
>(parent
)) {
463 // } else if (isa<UnresolvedMemberExpr>(parent)) {
465 // } else if (isa<PackExpansionExpr>(parent)) {
467 } else if (isa
<ExprWithCleanups
>(parent
)) {
468 return checkIfCanBeConst(parent
, cxxMethodDecl
);
469 // } else if (isa<CaseStmt>(parent)) {
471 // } else if (isa<CXXPseudoDestructorExpr>(parent)) {
473 // } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
475 // } else if (isa<ObjCIvarRefExpr>(parent)) {
476 // return checkIfCanBeConst(parent, cxxMethodDecl);
477 } else if (isa
<CXXTemporaryObjectExpr
>(parent
)) {
479 } else if (isa
<CXXBindTemporaryExpr
>(parent
)) {
484 // if (cxxMethodDecl)
485 // cxxMethodDecl->dump();
487 DiagnosticsEngine::Warning
,
488 "oh dear, what can the matter be?",
489 compat::getBeginLoc(parent
))
490 << parent
->getSourceRange();
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());
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());
514 loplugin::Plugin::Registration
< ConstMethod
> X("constmethod", false);
518 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */