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.
33 static bool startswith(const std::string
& rStr
, const char* pSubStr
) {
34 return rStr
.compare(0, strlen(pSubStr
), pSubStr
) == 0;
38 public loplugin::FunctionAddress
<ConstMethod
>
41 explicit ConstMethod(InstantiationData
const & data
): loplugin::FunctionAddress
<ConstMethod
>(data
) {}
43 virtual void run() override
{
44 std::string
fn( compiler
.getSourceManager().getFileEntryForID(
45 compiler
.getSourceManager().getMainFileID())->getName() );
46 normalizeDotDotInFilePath(fn
);
47 if (fn
.find("/qa/") != std::string::npos
)
49 // the rest of the stuff in these folders is technically const, but not logically const (IMO)
50 if (startswith(fn
, SRCDIR
"/unotools/"))
52 if (startswith(fn
, SRCDIR
"/svl/"))
54 if (startswith(fn
, SRCDIR
"/binaryurp/"))
56 if (startswith(fn
, SRCDIR
"/cpputools/"))
58 if (startswith(fn
, SRCDIR
"/opencl/"))
60 if (startswith(fn
, SRCDIR
"/helpcompiler/"))
62 // very little in here can usefully be made const. Could tighten this up a little and only exclude stuff declared in .cxx files
63 if (startswith(fn
, SRCDIR
"/vcl/"))
65 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
67 for (const CXXMethodDecl
*pMethodDecl
: interestingMethodSet
) {
68 if (methodCannotBeConstSet
.find(pMethodDecl
) != methodCannotBeConstSet
.end())
70 auto canonicalDecl
= pMethodDecl
->getCanonicalDecl();
71 if (getFunctionsWithAddressTaken().find((FunctionDecl
const *)canonicalDecl
)
72 != getFunctionsWithAddressTaken().end())
74 StringRef aFileName
= compiler
.getSourceManager().getFilename(compiler
.getSourceManager().getSpellingLoc(canonicalDecl
->getLocStart()));
75 if (loplugin::isSamePathname(aFileName
, SRCDIR
"/include/LibreOfficeKit/LibreOfficeKit.hxx"))
78 DiagnosticsEngine::Warning
,
79 "this method can be const",
80 pMethodDecl
->getLocStart())
81 << pMethodDecl
->getSourceRange();
82 if (canonicalDecl
->getLocation() != pMethodDecl
->getLocation()) {
84 DiagnosticsEngine::Note
,
85 "canonical method declaration here",
86 canonicalDecl
->getLocStart())
87 << canonicalDecl
->getSourceRange();
89 //pMethodDecl->dump();
93 bool TraverseCXXMethodDecl(CXXMethodDecl
*);
94 bool TraverseCXXConversionDecl(CXXConversionDecl
*);
95 bool VisitCXXMethodDecl(const CXXMethodDecl
*);
96 bool VisitCXXThisExpr(const CXXThisExpr
*);
99 bool isPointerOrReferenceToConst(const QualType
& qt
);
100 bool isPointerOrReferenceToNonConst(const QualType
& qt
);
101 bool checkIfCanBeConst(const Stmt
*, const CXXMethodDecl
*);
103 std::unordered_set
<const CXXMethodDecl
*> interestingMethodSet
;
104 std::unordered_set
<const CXXMethodDecl
*> methodCannotBeConstSet
;
105 CXXMethodDecl
const * currCXXMethodDecl
;
108 bool ConstMethod::TraverseCXXMethodDecl(CXXMethodDecl
* cxxMethodDecl
)
110 currCXXMethodDecl
= cxxMethodDecl
;
111 bool rv
= RecursiveASTVisitor
<ConstMethod
>::TraverseCXXMethodDecl(cxxMethodDecl
);
112 currCXXMethodDecl
= nullptr;
116 bool ConstMethod::TraverseCXXConversionDecl(CXXConversionDecl
* cxxConversionDecl
)
118 currCXXMethodDecl
= cxxConversionDecl
;
119 bool rv
= RecursiveASTVisitor
<ConstMethod
>::TraverseCXXConversionDecl(cxxConversionDecl
);
120 currCXXMethodDecl
= nullptr;
124 bool ConstMethod::VisitCXXMethodDecl(const CXXMethodDecl
* cxxMethodDecl
)
126 if (ignoreLocation(cxxMethodDecl
) || !cxxMethodDecl
->isThisDeclarationADefinition()) {
129 if (cxxMethodDecl
->isConst())
131 // ignore stuff that forms part of the stable URE interface
132 if (isInUnoIncludeFile(cxxMethodDecl
)) {
135 // TODO ignore template stuff for now
136 if (cxxMethodDecl
->getTemplatedKind() != FunctionDecl::TK_NonTemplate
) {
139 if (cxxMethodDecl
->isDeleted())
141 if (cxxMethodDecl
->isStatic())
143 if (cxxMethodDecl
->isOverloadedOperator())
145 if (isa
<CXXConstructorDecl
>(cxxMethodDecl
))
147 if (isa
<CXXDestructorDecl
>(cxxMethodDecl
))
149 if (cxxMethodDecl
->getParent()->getDescribedClassTemplate() != nullptr ) {
152 // ignore virtual methods
153 if (cxxMethodDecl
->isVirtual() ) {
156 // ignore macro expansions so we can ignore the IMPL_LINK macros from include/tools/link.hxx
157 // TODO make this more precise
158 if (cxxMethodDecl
->getLocation().isMacroID())
161 if (!cxxMethodDecl
->getIdentifier())
164 StringRef name
= cxxMethodDecl
->getName();
165 if (name
== "PAGE") // otherwise we have two methods that only differ in their return type
167 // stuff in comphelper I'm not sure about
168 if (name
== "CommitImageSubStorage" || name
== "CloseEmbeddedObjects" || name
== "flush")
171 interestingMethodSet
.insert(cxxMethodDecl
);
176 bool ConstMethod::VisitCXXThisExpr( const CXXThisExpr
* cxxThisExpr
)
178 if (!currCXXMethodDecl
)
180 if (ignoreLocation(cxxThisExpr
))
182 // ignore stuff that forms part of the stable URE interface
183 if (isInUnoIncludeFile(cxxThisExpr
->getLocStart()))
185 if (interestingMethodSet
.find(currCXXMethodDecl
) == interestingMethodSet
.end())
187 // no need to check again if we have already eliminated this one
188 if (methodCannotBeConstSet
.find(currCXXMethodDecl
) != methodCannotBeConstSet
.end())
190 if (!checkIfCanBeConst(cxxThisExpr
, currCXXMethodDecl
))
191 methodCannotBeConstSet
.insert(currCXXMethodDecl
);
196 // Walk up from a statement that contains a CXXThisExpr, checking if the usage means that the
197 // related CXXMethodDecl can be const.
198 bool ConstMethod::checkIfCanBeConst(const Stmt
* stmt
, const CXXMethodDecl
* cxxMethodDecl
)
200 const Stmt
* parent
= getParentStmt( stmt
);
202 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
203 if ( parentsRange
.begin() == parentsRange
.end())
205 auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>());
209 DiagnosticsEngine::Warning
,
212 << stmt
->getSourceRange();
215 return varDecl
->getType()->isIntegralOrEnumerationType()
216 || loplugin::TypeCheck(varDecl
->getType()).Pointer().Const()
217 || loplugin::TypeCheck(varDecl
->getType()).LvalueReference().Const();
220 if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
)) {
221 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
222 if (op
== UO_AddrOf
|| op
== UO_PreInc
|| op
== UO_PostInc
223 || op
== UO_PreDec
|| op
== UO_PostDec
) {
226 if (op
== UO_Deref
) {
227 return checkIfCanBeConst(parent
, cxxMethodDecl
);
230 } else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
)) {
231 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
232 if (binaryOp
->getRHS() == stmt
) {
235 if (op
== BO_Assign
|| op
== BO_PtrMemD
|| op
== BO_PtrMemI
|| op
== BO_MulAssign
236 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
237 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
238 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
) {
241 // // for pointer arithmetic need to check parent
242 // if (binaryOp->getType()->isPointerType()) {
243 // return checkIfCanBeConst(parent, cxxMethodDecl);
246 } else if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(parent
)) {
247 const CXXConstructorDecl
* constructorDecl
= constructExpr
->getConstructor();
248 for (unsigned i
= 0; i
< constructExpr
->getNumArgs(); ++i
) {
249 if (constructExpr
->getArg(i
) == stmt
) {
250 return isPointerOrReferenceToConst(constructorDecl
->getParamDecl(i
)->getType());
253 return false; // TODO ??
254 } else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
)) {
255 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(operatorCallExpr
->getDirectCallee());
256 if (calleeMethodDecl
) {
258 if (calleeMethodDecl
->getNumParams() == 0) {
259 // some classes like std::unique_ptr do not do a very good job with their operator-> which is always const
260 if (operatorCallExpr
->getOperator() == OO_Arrow
|| operatorCallExpr
->getOperator() == OO_Star
) {
261 return checkIfCanBeConst(parent
, cxxMethodDecl
);
263 return calleeMethodDecl
->isConst();
265 // some classes like std::unique_ptr do not do a very good job with their operator[] which is always const
266 if (calleeMethodDecl
->getNumParams() == 1 && operatorCallExpr
->getArg(0) == stmt
) {
267 if (operatorCallExpr
->getOperator() == OO_Subscript
) {
272 if (operatorCallExpr
->getArg(0) == stmt
) {
273 return calleeMethodDecl
->isConst();
275 unsigned const n
= std::min(
276 operatorCallExpr
->getNumArgs(),
277 calleeMethodDecl
->getNumParams());
278 for (unsigned i
= 1; i
< n
; ++i
)
279 if (operatorCallExpr
->getArg(i
) == stmt
) {
280 return isPointerOrReferenceToConst(calleeMethodDecl
->getParamDecl(i
- 1)->getType());
283 const Expr
* callee
= operatorCallExpr
->getCallee()->IgnoreParenImpCasts();
284 const DeclRefExpr
* dr
= dyn_cast
<DeclRefExpr
>(callee
);
285 const FunctionDecl
* calleeFunctionDecl
= nullptr;
287 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
289 if (calleeFunctionDecl
) {
290 for (unsigned i
= 0; i
< operatorCallExpr
->getNumArgs(); ++i
) {
291 if (operatorCallExpr
->getArg(i
) == stmt
) {
292 return isPointerOrReferenceToConst(calleeFunctionDecl
->getParamDecl(i
)->getType());
297 return false; // TODO ???
298 } else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
)) {
299 QualType functionType
= callExpr
->getCallee()->getType();
300 if (functionType
->isFunctionPointerType()) {
301 functionType
= functionType
->getPointeeType();
303 if (const FunctionProtoType
* prototype
= functionType
->getAs
<FunctionProtoType
>()) {
304 // TODO could do better
305 if (prototype
->isVariadic()) {
308 if (callExpr
->getCallee() == stmt
) {
311 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
312 if (callExpr
->getArg(i
) == stmt
) {
313 return isPointerOrReferenceToConst(prototype
->getParamType(i
));
317 const FunctionDecl
* calleeFunctionDecl
= callExpr
->getDirectCallee();
318 if (calleeFunctionDecl
)
320 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
)) {
321 const MemberExpr
* memberExpr
= dyn_cast
<MemberExpr
>(stmt
);
322 if (memberExpr
&& memberCallExpr
->getImplicitObjectArgument() == memberExpr
->getBase())
324 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast
<CXXMethodDecl
>(calleeFunctionDecl
);
325 // some classes like std::unique_ptr do not do a very good job with their get() which is always const
326 if (calleeMethodDecl
->getIdentifier() && calleeMethodDecl
->getName() == "get") {
327 return checkIfCanBeConst(parent
, cxxMethodDecl
);
329 // VclPtr<T>'s implicit conversion to T*
330 if (isa
<CXXConversionDecl
>(calleeMethodDecl
)) {
331 if (loplugin::DeclCheck(calleeMethodDecl
->getParent()).Class("OWeakObject").Namespace("cppu").GlobalNamespace())
333 return checkIfCanBeConst(parent
, cxxMethodDecl
);
335 return calleeMethodDecl
->isConst();
338 // TODO could do better
339 if (calleeFunctionDecl
->isVariadic()) {
342 if (callExpr
->getCallee() == stmt
) {
345 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
346 if (i
>= calleeFunctionDecl
->getNumParams()) // can happen in template code
348 if (callExpr
->getArg(i
) == stmt
) {
349 return isPointerOrReferenceToConst(calleeFunctionDecl
->getParamDecl(i
)->getType());
353 return false; // TODO ????
354 // } else if (auto callExpr = dyn_cast<ObjCMessageExpr>(parent)) {
355 // if (callExpr->getInstanceReceiver() == stmt) {
358 // if (auto const method = callExpr->getMethodDecl()) {
359 // // TODO could do better
360 // if (method->isVariadic()) {
363 // assert(method->param_size() == callExpr->getNumArgs());
364 // for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
365 // if (callExpr->getArg(i) == stmt) {
366 // return isPointerOrReferenceToConst(
367 // method->param_begin()[i]->getType());
371 // return false; // TODO ????
372 } else if (isa
<CXXReinterpretCastExpr
>(parent
)) {
374 } else if (isa
<ImplicitCastExpr
>(parent
)) {
375 return checkIfCanBeConst(parent
, cxxMethodDecl
);
376 } else if (isa
<CXXStaticCastExpr
>(parent
)) {
377 return checkIfCanBeConst(parent
, cxxMethodDecl
);
378 } else if (isa
<CXXDynamicCastExpr
>(parent
)) {
379 return checkIfCanBeConst(parent
, cxxMethodDecl
);
380 } else if (isa
<CXXFunctionalCastExpr
>(parent
)) {
381 return checkIfCanBeConst(parent
, cxxMethodDecl
);
382 } else if (isa
<CXXConstCastExpr
>(parent
)) {
384 } else if (isa
<CStyleCastExpr
>(parent
)) {
385 return checkIfCanBeConst(parent
, cxxMethodDecl
);
386 // } else if (isa<CastExpr>(parent)) { // all other cast expression subtypes
387 // if (auto e = dyn_cast<ExplicitCastExpr>(parent)) {
388 // if (loplugin::TypeCheck(e->getTypeAsWritten()).Void()) {
389 // if (auto const sub = dyn_cast<DeclRefExpr>(
390 // e->getSubExpr()->IgnoreParenImpCasts()))
392 // if (sub->getDecl() == cxxMethodDecl) {
398 // return checkIfCanBeConst(parent, cxxMethodDecl);
399 } else if (isa
<MemberExpr
>(parent
)) {
400 return checkIfCanBeConst(parent
, cxxMethodDecl
);
401 } else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
)) {
402 if (arraySubscriptExpr
->getIdx() == stmt
)
404 return checkIfCanBeConst(parent
, cxxMethodDecl
);
405 } else if (isa
<ParenExpr
>(parent
)) {
406 return checkIfCanBeConst(parent
, cxxMethodDecl
);
407 } else if (auto declStmt
= dyn_cast
<DeclStmt
>(parent
)) {
408 for (Decl
const * decl
: declStmt
->decls())
409 if (auto varDecl
= dyn_cast
<VarDecl
>(decl
)) {
410 if (varDecl
->getInit() == stmt
) {
411 auto tc
= loplugin::TypeCheck(varDecl
->getType());
412 if (tc
.LvalueReference() && !tc
.LvalueReference().Const())
414 if (tc
.Pointer() && !tc
.Pointer().Const())
420 } else if (isa
<ReturnStmt
>(parent
)) {
421 return !isPointerOrReferenceToNonConst(cxxMethodDecl
->getReturnType());
422 } else if (isa
<InitListExpr
>(parent
)) {
423 return false; // TODO could be improved
424 } else if (isa
<IfStmt
>(parent
)) {
426 } else if (isa
<WhileStmt
>(parent
)) {
428 } else if (isa
<ForStmt
>(parent
)) {
430 } else if (isa
<CompoundStmt
>(parent
)) {
432 } else if (isa
<SwitchStmt
>(parent
)) {
434 } else if (isa
<DoStmt
>(parent
)) {
436 } else if (isa
<CXXDeleteExpr
>(parent
)) {
438 // } else if (isa<VAArgExpr>(parent)) {
440 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
442 } else if (isa
<MaterializeTemporaryExpr
>(parent
)) {
443 return checkIfCanBeConst(parent
, cxxMethodDecl
);
444 } else if (auto conditionalExpr
= dyn_cast
<ConditionalOperator
>(parent
)) {
445 if (conditionalExpr
->getCond() == stmt
)
447 return checkIfCanBeConst(parent
, cxxMethodDecl
);
448 // } else if (isa<UnaryExprOrTypeTraitExpr>(parent)) {
449 // return false; // ???
450 } else if (auto cxxNewExpr
= dyn_cast
<CXXNewExpr
>(parent
)) {
451 // for (auto pa : cxxNewExpr->placement_arguments())
454 return true; // because the Stmt must be a parameter to the expression, probably an array length
455 // } else if (auto lambdaExpr = dyn_cast<LambdaExpr>(parent)) {
456 //// for (auto it = lambdaExpr->capture_begin(); it != lambdaExpr->capture_end(); ++it)
458 //// if (it->capturesVariable() && it->getCapturedVar() == cxxMethodDecl)
459 //// return it->getCaptureKind() != LCK_ByRef;
462 // } else if (isa<CXXTypeidExpr>(parent)) {
464 } else if (isa
<ParenListExpr
>(parent
)) {
466 } else if (isa
<CXXUnresolvedConstructExpr
>(parent
)) {
468 // } else if (isa<UnresolvedMemberExpr>(parent)) {
470 // } else if (isa<PackExpansionExpr>(parent)) {
472 } else if (isa
<ExprWithCleanups
>(parent
)) {
473 return checkIfCanBeConst(parent
, cxxMethodDecl
);
474 // } else if (isa<CaseStmt>(parent)) {
476 // } else if (isa<CXXPseudoDestructorExpr>(parent)) {
478 // } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
480 // } else if (isa<ObjCIvarRefExpr>(parent)) {
481 // return checkIfCanBeConst(parent, cxxMethodDecl);
482 } else if (isa
<CXXTemporaryObjectExpr
>(parent
)) {
484 } else if (isa
<CXXBindTemporaryExpr
>(parent
)) {
489 // if (cxxMethodDecl)
490 // cxxMethodDecl->dump();
492 DiagnosticsEngine::Warning
,
493 "oh dear, what can the matter be?",
494 parent
->getLocStart())
495 << parent
->getSourceRange();
499 bool ConstMethod::isPointerOrReferenceToConst(const QualType
& qt
) {
500 auto const type
= loplugin::TypeCheck(qt
);
501 if (type
.Pointer()) {
502 return bool(type
.Pointer().Const());
503 } else if (type
.LvalueReference()) {
504 return bool(type
.LvalueReference().Const());
509 bool ConstMethod::isPointerOrReferenceToNonConst(const QualType
& qt
) {
510 auto const type
= loplugin::TypeCheck(qt
);
511 if (type
.Pointer()) {
512 return !bool(type
.Pointer().Const());
513 } else if (type
.LvalueReference()) {
514 return !bool(type
.LvalueReference().Const());
519 loplugin::Plugin::Registration
< ConstMethod
> X("constmethod", true);
523 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */